Linux驱动-应用层如何访问驱动层- 以应用层open函数对应驱动层open函数为例
在Linux系统中,应用层的open函数与驱动层的open函数之间的关联,与驱动模块的加载和设备节点的创建密切相关。
一、驱动模块的加载
-
驱动模块概述
驱动模块是一个动态可加载的内核模块,它包含了设备特定的操作函数,包括open、read、write等。驱动模块的加载是设备与操作系统交互的第一步。 -
模块加载过程
加载驱动模块通常涉及以下步骤:
编译驱动模块:驱动程序代码被编译成一个.ko文件。
加载驱动模块:使用insmod或modprobe命令将模块加载到内核中。在这个过程中,内核会调用驱动模块的初始化函数。
示例:加载驱动模块
insmod my_driver.ko
- 注册设备
在驱动模块加载过程中,驱动会注册设备,关联设备号和操作函数。以字符设备为例,注册过程通常包括以下内容:
分配或指定主设备号:主设备号用于标识设备驱动。
注册设备:通过register_chrdev函数,将主设备号与一个file_operations结构关联。
示例:设备注册
#define MAJOR_NUM 240
static int __init my_driver_init(void) {
int ret;
ret = register_chrdev(MAJOR_NUM, "my_device", &fops);
if (ret < 0) {
printk(KERN_ALERT "Device registration failed\n");
return ret;
}
printk(KERN_INFO "Device registered with major number %d\n", MAJOR_NUM);
return 0;
}
在这里,register_chrdev函数注册了一个主设备号240,并将其与名为"my_device"的设备及其操作函数集fops关联。
- file_operations结构
file_operations结构体包含了设备的各种操作函数,包括open函数。
例如:
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = my_driver_open,
// 其他操作函数如 read, write 等
};
当驱动模块注册时,这个结构体将被内核用于管理设备的各种操作。
二、创建设备节点
-
设备节点概述
设备节点是位于/dev目录下的特殊文件,用户态程序通过它们访问设备。设备节点包含主设备号和次设备号,用于映射到内核中的设备驱动。 -
设备节点创建
设备节点可以通过以下方式创建:
手动创建:使用mknod命令创建设备节点。
自动创建:通过udev等设备管理服务自动创建。
示例:手动创建设备节点
mknod /dev/my_device c 240 0
这条命令创建了一个名为/dev/my_device的字符设备节点,其主设备号为240,次设备号为0。
- 设备节点的作用
设备节点的主设备号和次设备号用于在内核中定位到对应的驱动程序。主设备号标识设备类型和对应的驱动程序,而次设备号用于区分同一类型的多个设备实例。
三、应用层的open调用
- 用户态调用open
当应用程序调用open(“/dev/my_device”, O_RDWR)时,系统调用从用户态切换到内核态,具体流程如下:
用户态的open调用进入标准C库(glibc)的open函数。
标准库函数通过系统调用sys_open将请求传递到内核。 - 内核处理系统调用
在内核中,sys_open函数负责处理open请求:路径解析:内核解析传入的文件路径(例如/dev/my_device)。
查找设备节点:内核找到与该路径对应的设备节点,并读取其主设备号和次设备号。(之前通过mknod /dev/my_device c 240 0创建过设备节点,所以可以顺利读取到) - 查找对应的驱动
内核使用设备节点的主设备号找到对应的驱动程序:
定位驱动程序:通过设备号查找在驱动加载时注册的设备和其file_operations结构。(在insmod加载驱动时,会调用__init初始化函数,这个函数里面包含了注册设备,将设备号与一个file_operations结构关联)
调用驱动的open函数:找到驱动的file_operations结构后,内核调用其中的open函数。 - 驱动层的open函数
驱动层的open函数在file_operations结构中定义。例如:
static int my_driver_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "My driver open function called\n");
return 0; // 成功
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = my_driver_open,
// 其他操作函数
};
当内核调用驱动层的open函数时,它实际上调用了my_driver_open函数。
四、流程图
应用层:
open("/dev/my_device") ----> 标准库 (glibc)
内核态:
sys_open() ----> 路径解析 /dev/my_device
----> 查找设备节点 (主设备号 240, 次设备号 0)
----> 根据主设备号找到对应的驱动
----> 调用驱动的 file_operations.open (my_driver_open)
驱动层:
my_driver_open()
通过上述机制,Linux系统成功将用户态的open调用映射到驱动层的open函数,实现了设备的访问和操作。这一切依赖于驱动模块的正确加载和设备节点的准确创建。