今天在复习指针的时候,想起了最近要考的驱动程序代码。当我看到函数指针这个东西的时候,突然就想明白一个问题:
驱动程序到底是如何工作的——当我们加载模块后,我们在用户程序中如何访问到了它?
从一个简单的空的字符设备驱动开始,代码结构如下:
头文件
#include <linux/init.h> #include <linux/module.h> #include <linux/sched.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/types.h>
用于分配设备号的,用于内核表示字符设备的结构体
int major=0; //主设备号 int minor=0; //次设备号 char * name ="em_chrdev"; //驱动设备名,在/proc/devices中出现 struct cdev *cdev; //内核表示字符设备的结构体 struct file_operations fops = { .owner = THIS_MODULE, .open = sc_open, .release = sc_release, .write = sc_write, .read = sc_read, };
初始化函数
退出函数
在初始化函数中我们可以说只做了一件事,那就是初始化cdev结构体。
cdev结构体的定义:(引用自:https://blog.csdn.net/zqixiao_09/article/details/50839042)
- <include/linux/cdev.h>
- struct cdev {
- struct kobject kobj; //内嵌的内核对象.
- struct module *owner; //该字符设备所在的内核模块的对象指针.
- const struct file_operations *ops; //该结构描述了字符设备所能实现的方法,是极为关键的一个结构体.
- struct list_head list; //用来将已经向内核注册的所有字符设备形成链表.
- dev_t dev; //字符设备的设备号,由主设备号和次设备号构成.
- unsigned int count; //隶属于同一主设备号的次设备号的个数.
- };
第一个,不管(因为我也不知道是做什么的)
第二个,设置为THIS_MODUL
第三个,一个file_operations结构体,它用来定义驱动程序操作,如read,write,open。实际上我们的大部分代码都在实现这个结构体的各种方法。
第四个,没有涉及到
第五个,一个代表设备号的结构体,包括主设备号和此设备号,dev_t本身是一个32位数(12位主设备号和20位次设备号)
第六个,次设备号的个数
因此我们看到初始化程序是这个样子的
static int __init hello_init(void) { int ret=0; dev_t dev_no; //设备号 dev_no=MKDEV(major,minor); //一个用来将两个整形数转换为dev_t结构体的宏 //register device number if(major) { ret=register_chrdev_region(dev_no,1,name);//一个用来注册字符设备编号的函数, if(ret<0) { printk(KERN_ALERT"register_chrdev_region error\n"); goto out; } } else { ret=alloc_chrdev_region(&dev_no,0,1,name); if(ret<0) { printk(KERN_ALERT"alloc_chrdev_region error\n"); goto out; } major =MAJOR(dev_no); } //注册设备结构体 cdev =cdev_alloc();//为cdev分配内存空间 if(cdev==NULL) { printk(KERN_ALERT"cdev_alloc error\n"); ret=ENOMEM; goto out; } cdev->ops=&fops; //对应第三个,将ops赋值为fops cdev->owner = THIS_MODULE;//对应第二个,设置所有者字段 ret=cdev_add(cdev,dev_no,1);//在这一步中完成了第5和第6 if(ret) { printk(KERN_ALERT"cdev_add error\n"); goto out; } out: return ret; }
而退出函数大体上只调用了一个cdev_del函数,这个函数移除了该字符设备。
其余的代码如下:
结合file_operations结构体的定义:(参考linux/fs.h)
如.open,对应的是一个函数指针:int (*open)(struct inode *,struct file *)。因此我们定义的sc_open函数将会被赋给该指针!!!!
这倒是很像C#语言中的委托,linux系统帮我们设置了一个个接口,而我们对驱动的操作则通过实现这些接口来达成。
因此,当我们在用户程序中 open,read,实际上就是在调用这些函数。