Linux驱动背景知识
Linux文件系统由4层组成:用户层、内核层、驱动层、硬件层。
- 用户层:为用户提供操作接口
- 内核层:实现各种文件系统
- 驱动层:各种设备的驱动程序
- 硬件层:存储器和各种硬件外设
设备驱动程序的作用: 驱动程序是提供硬件到操作系统的一个接口,并且协调二者之间的关系。是硬件和系统之间的桥梁。
设备驱动的分类: 字符设备、块设备、网络设备
字符设备: 能一个字节一个字节读取数据的设备,一般需要在驱动层实现open()、close()、read()、write()、ioctl()等函数。这些函数最终被文件系统中的相关函数调用。内核为字符设备对应一个文件,/dev/xxx。对字符设备的操作可以用字符设备/dev/xxx来进行。字符设备一般不支持寻址,但特殊情况下,有很多字符设备也是支持寻址的。寻址的意思是,对硬件中一块寄存器进行随机地访问。不支持寻址就是只能对硬件中的寄存器进行顺序的读取,读取数据后,由驱动程序自己分析需要哪一部分数据。
模块机制: 模块是可以在运行时加入内核的代码。模块在内核启动时装载称为静态装载,在内核已经运行时装载称为动态装载。 可以使用insmod(insert module的缩写)命令将模块加入正在运行的内核,也可以使用rmmod(remove module的缩写)命令将一个未使用的模块从内核中删除。试图删除一个正在使用的模块,将是不允许的。
一个模块的最基本框架代码如下:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
int __init xxx_init(void) {
/*这里是模块加载时的初始化工作*/
return 0;
}
void __exit xxx_exit(void) {
/*这里是模块卸载时的销毁工作*/
}
module_init(xxx_init); /*指定模块的初始化函数的宏*/
module_exit(xxx_exit); /*指定模块的卸载函数的宏*/
Linux中的file_operations结构体
- file_operation就是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用。通过file_operations中相应的函数指针,把控制权转交给函数,从而完成了Linux设备驱动程序的工作。
- 在你通读 file_operations 方法的列表时, 你会注意到不少参数包含字串 __user. 这种注解是一种文档形式, 注意, 一个指针是一个不能被直接解引用的用户空间地址. 对于正常的编译, __user 没有效果, 但是它可被外部检查软件使用来找出对用户空间地址的错误使用。
- 大部分的基础性的驱动操作包括3个重要的内核数据结构,称为file_operations、file、inode:
- struct file_operations是一个字符设备把驱动的操作和设备号联系在一起的纽带,是一系列指针的集合,每个被打开的文件都对应于一系列的操作,这就是file_operations,用来执行一系列的系统调用。
- struct file代表一个打开的文件,在执行file_operations中的open操作时被创建,这里需要注意的是与用户空间inode指针的区别,一个在内核,而file指针在用户空间,由c库来定义。
- struct inode被内核用来代表一个文件,注意和struct file的区别,struct inode一个是代表文件,struct file一个是代表打开的文件
- struct inode结构是用来在内核内部表示文件的.同一个文件可以被打开好多次,所以可以对应很多struct file,但是只对应一个struct inode
- 几个Binder涉及到的函数说明:
- open 尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
- flush 操作在进程关闭它的设备文件描述符的拷贝时调用;
- release 当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数
- mmap 用来请求将设备内存映射到进程的地址空间。
- ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写)
- poll 返回设备资源的可获取状态即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位“或”结果。每个宏都表明设备的一种状态,如:POLLIN(定义为0x0001)意味着设备可以无阻塞的读,POLLOUT(定义为0x0004)意味着设备可以无阻塞的写。
- owner 一个指向拥有这个结构的模块的指针,这个成员用来在它的操作还在被使用时阻止模块被卸载.
Binder驱动
Binder驱动属于字符设备,而且是静态装载,只有模块初始化函数,没有模块卸载函数
/* module_init宏解开就是device_initcall,所以这里等价于module_init(binder_init) */
device_initcall(binder_init); /* binder驱动的初始化函数 */
Binder驱动层实现的函数
static const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
static struct miscdevice binder_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "binder",
.fops = &binder_fops
};
关键函数说明
binder_init
创建一个binder的内核线程队列,并创建binder驱动设备文件节点
/* binder驱动模块初始化函数 */
static int __init binder_init(void)
{
int ret;
/* 创建一个专用的内核线程来执行提交到工作队列中的函数 */
binder_deferred_workqueue = create_singlethread_workqueue("binder");
if (!binder_deferred_workqueue)
return -ENOMEM;
/* 注册binder驱动程序 */
/* misc_device是特殊字符设备,采用misc_register函数注册
* 此函数中会自动创建设备节点,即设备文件。无需mknod指令创建设备文件。
*/
ret = misc_register(&binder_miscdev);
return ret;
}
binder_open
创建并初始化binder_proc对象(保存当前进程信息),并将binder_proc加入全局哈希表binder_procs队列中,同时把binder_proc对象保存到文件指针filp->private_data
/* 用户空间程序打开"/dev/binder"时,内核空间binder驱动程序将执行此函数进行open操作 */
static int binder_open(struct inode *nodp, struct file *filp)
{
/**
* binder_proc是内核中描述Binder进程上下文信息的结构体,其在用户空间相对应的是ProcessState。
* Binder驱动的文件节点是"/dev/binder",每当一个程序打开该文件节点时;
* Binder驱动中都会新建一个binder_proc对象来保存该进程的上下文信息。
* /
struct binder_proc *proc;
/* 为proc分配内存空间并初始化为0 */
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
if (proc == NULL)
return -ENOMEM;
/* 增加线程引用计数,防止current被释放 */
get_task_struct(current);
proc->tsk = current;
/* 初始化进程的待处理事件队列 */
INIT_LIST_HEAD(&proc->todo);
/* 初始化进程的等待队列 */
init_waitqueue_head(&proc->wait);
/* 获取默认优先级(获取进程的nice值,nice值其实代表进程的优先级)*/
proc->default_priority = task_nice(current);
binder_lock(__func__);
/* BINDER_PROC 对象创建数加1 */
binder_stats_created(BINDER_STAT_PROC);
/* 将 proc_node 节点添加到全局哈希表binder_procs队列头,(binder_procs统计了所有的binder proc进程)*/
hlist_add_head(&proc->proc_node, &binder_procs);
proc->pid = current->group_leader->pid;
/* 初始化已分发的死亡通知列表 */
INIT_LIST_HEAD(&proc->delivered_death);
/* file 文件指针的 private_data 变量指向 binder_proc 数据 */
filp->private_data = proc;
binder_unlock(__func__);
return 0;
}
binder_mmap
binder_mmap 的主要作用就是开辟一块连续的内核虚拟地址空间,并且开辟一个物理页的地址空间,同时映射到用户空间和内核空间。 总结binder_mmap顺序:
- 检测内存映射条件,即映射内存大小(4MB),flags,是否已经映射过;
- 获取内核虚拟地址空间,并把此空间的地址记录到进程信息buffer中;
- 分配一个物理页面并记录下来;
- 将buffer插入到进程信息的buffer列表中;
- 调用binder_update_page_range函数将分配的物理页面和vm空间对应起来;
- 通过binder_insert_free_buffer函数把此进程的buffer插入到进程信息中。
/* vm_area_struct 用户态虚拟地址空间,vm_struct 内核虚拟地址空间*/
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
/* vm_struct 内核虚拟地址空间, vm_area_struct 用户态虚拟地址空间 */
struct vm_struct *area;
struct binder_proc *proc = filp->private_data;
const char *failure_string;
struct binder_buffer *buffer;
if (proc->tsk != current)
return -EINVAL;
/* 保证映射内存大小不超过4M */
if ((vma->vm_end - vma->vm_start) > SZ_4M)
vma->vm_end = vma->vm_start + SZ_4M;
/* 检查 vma 是否被禁用 */
if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
ret = -EPERM;
failure_string = "bad vm_flags";
goto err_bad_arg;
}
vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;
mutex_lock(&binder_mmap_lock);
/* 检查是否已执行过 binder_mmap 映射过 */
if (proc->buffer) {
ret = -EBUSY;
failure_string = "already mapped";
goto err_already_mapped;
}
/* 申请内核虚拟内存地址空间 */
area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
if (area == NULL) {
ret = -ENOMEM;
failure_string = "get_vm_area";
goto err_get_vm_area_failed;
}
/* 将内核虚拟内存地址记录在 proc 中 */
proc->buffer = area->addr;
/* 记录用户态虚拟内存地址和内核态虚拟内存地址的偏移量(地址偏移量 = 用户虚拟内存地址 - 内核虚拟内存地址)*/
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
mutex_unlock(&binder_mmap_lock);
/**
* kzalloc申请内存, 并被初始化为0
* 分配描述所有物理页面的数组,一个物理页面用page来表示,
* 缓存区所需物理页面个数为((vma->vm_end - vma->vm_start) / PAGE_SIZE),
* 所以描述所有物理页面信息需要
* sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE)
* 大小的存储空间
*/
proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
if (proc->pages == NULL) {
ret = -ENOMEM;
failure_string = "alloc page array";
goto err_alloc_pages_failed;
}
proc->buffer_size = vma->vm_end - vma->vm_start;
/* 注册进程虚拟地址空间的操作函数 */
vma->vm_ops = &binder_vm_ops;
/* 将当前进程的binder_proc注册到虚拟地址空间描述符的vm_private_data中 */
vma->vm_private_data = proc;
/* 分配物理页面,同时映射到内核空间和进程空间,先分配1个物理页 */
if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
ret = -ENOMEM;
failure_string = "alloc small buf";
goto err_alloc_small_buf_failed;
}
buffer = proc->buffer;
/* 初始化进程的 buffers 链表头 */
INIT_LIST_HEAD(&proc->buffers);
/* 将 binder_buffer 地址加入到所属进程的 buffers 队列 */
list_add(&buffer->entry, &proc->buffers);
buffer->free = 1;
/* 将空闲 buffer 放入 proc->free_buffers 中 */
binder_insert_free_buffer(proc, buffer);
/* 异步可用空间大小为 buffer 总大小的一半。 */
proc->free_async_space = proc->buffer_size / 2;
barrier();
proc->files = get_files_struct(current);
proc->vma = vma;
proc->vma_vm_mm = vma->vm_mm;
return 0;
}
binder_ioctl
binder ioctl中的命令有以下5个,定义在binder.h中,如下所示:
- #define BINDER_WRITE_READ _IOWR('b',1, struct binder_write_read)
- #define BINDER_SET_MAX_THREADS _IOW('b', 5, size_t)
- #define BINDER_SET_CONTEXT_MGR _IOW('b', 7, int)
- #define BINDER_THREAD_EXIT _IOW('b', 8, int)
- #define BINDER_VERSION _IOWR('b',9, struct binder_version)
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
/* 进入休眠状态,直到中断唤醒 */
ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
if (ret)
goto err_unlocked;
binder_lock(__func__);
/* 从binder_proc中取出binder_thread线程描述符 */
thread = binder_get_thread(proc);
if (thread == NULL) {
ret = -ENOMEM;
goto err;
}
/* 处理不同的binder命令 */
switch (cmd) {
/* 进行 binder 的读写操作 */
case BINDER_WRITE_READ:
ret = binder_ioctl_write_read(filp, cmd, arg, thread);
if (ret)
goto err;
break;
/* 设置 binder 最大支持的线程数 */
case BINDER_SET_MAX_THREADS:
if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
ret = -EINVAL;
goto err;
}
break;
/* 成为 binder 的上下文管理者,也就是 ServiceManager 成为守护进程 */
/**
* 若一个进程/线程能被成功设置成binder_context_mgr_node对象,称该进程/线程为Context Manager.
* 即设置驱动中的全局变量binder_context_mgr_uid为当前进程的uid.
* 并初始化一个Binder_node并赋值给全局变量binder_context_mgr_node
* 只有创建binder_context_mgr_node对象的Binder上下文管理进程/线程才有权限重新设置这个对象
case BINDER_SET_CONTEXT_MGR:
ret = binder_ioctl_set_ctx_mgr(filp);
if (ret)
goto err;
ret = security_binder_set_context_mgr(proc->tsk);
if (ret < 0)
goto err;
break;
/* 当 binder 线程退出,释放 binder 线程 */
case BINDER_THREAD_EXIT:
binder_debug(BINDER_DEBUG_THREADS, "%d:%d exit\n",
proc->pid, thread->pid);
binder_free_thread(proc, thread);
thread = NULL;
break;
/* 获取 binder 的版本号 */
case BINDER_VERSION: {
struct binder_version __user *ver = ubuf;
if (size != sizeof(struct binder_version)) {
ret = -EINVAL;
goto err;
}
if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
&ver->protocol_version)) {
ret = -EINVAL;
goto err;
}
break;
}
default:
ret = -EINVAL;
goto err;
}
ret = 0;
err:
if (thread)
thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
binder_unlock(__func__);
wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
if (ret && ret != -ERESTARTSYS)
pr_info("%d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
err_unlocked:
trace_binder_ioctl_done(ret);
return ret;
}