1、Linux 驱动开发学习的一些必要步骤1. 学会写简单的 makefile2. 编一应用程序,可以用 makefile 跑起来3. 学会写驱动的 makefile4. 写一简单 char 驱动,makefile 编译通过,可以 insmod, lsmod, rmmod. 在驱动的 init 函数里打印 hello world, insmod 后应该能够通过 dmesg 看到输出。5. 写一完整驱动, 加上 read, write, ioctl, polling 等各种函数的驱动实现。 在 ioctl 里完成从用户空间向内核空间传递结构体的实现。6. 写一 block 驱动, 加上 read,w
2、rite,ioctl,poll 等各种函数实现。7. 简单学习下内存管理, 这个是最难的,明白各种 memory alloc 的函数实现细节。这是 Linux 开发的基本功。8. 学习锁机制的应用,这个不是最难的但是最容易犯错的,涉及到很多同步和并发的问题。9. 看内核中实际应用的驱动代码。 你会发现最基本的你已经知道了, 大的框架都是一样的, 无非是 read, write, ioctl 等函数的实现, 但里面包含了很多很多细小的实现细节是之前不知道的。 这时候就要考虑到很多别的问题而不仅仅是基本功能的实现。 推荐您看 2.6.20 中 integrated 的一个驱动 kvm, 记得是在
3、driver/lguest 下,很好玩的, 就是 Linux 下的虚拟机驱动, 代码不长,但功能强大。有能力的可以自己写一操作系统按照要求做成磁盘镜像加载到虚拟机中, 然后客户机可以有自己的 4G 虚拟地址空间。10. 看完驱动欢迎您进入 Linux kernel 学习中来。 最简单的方法,跟着 ldd(Linux devive driver)做一遍。1、 Makefile 是如何编写eg: # 这是上面那个程序的 Makefile 文件 1main:main.o mytool1.o mytool2.o 2gcc -o main main.o mytool1.o mytool2.o 3main
4、.o:main.c mytool1.h mytool2.h 4gcc -c main.c 5mytool1.o:mytool1.c mytool1.h 6gcc -c mytool1.c 7mytool2.o:mytool2.c mytool2.h 8gcc -c mytool2.c 9分析:在 Makefile 中也# 开始的行都是注释行.Makefile 中最重要的是描述文件的依赖关系的说 明.一般的格式是: Linux 操作系统 C 语言编程入门 target: components /表示的是依赖关系TAB rule /规则main:main.o mytool1.o mytool2.o
5、 表示我们的目标(target)main 的依赖对象(components) 是 main.o mytool1.o mytool2.o 当倚赖的对象在目标修改后修改的话,就要去执行规则一行所指定的命令.就象我们的上 面那个 Makefile 第 3 行所说的一样要执行 gcc -o main main.o mytool1.o mytool2.o (注意规则一行中的 TAB 表示那里是一个 TAB 键)Makefile 有三个非常有用的变量.分别是$,$,$list); 5 cdev-kobj.ktype = 6 kobject_init( 7 cdev-ops = fops; /*将传入的文件
6、操作结构体指针赋值给 cdev 的 ops*/ 8 cdev_alloc()函数用于动态申请一个 cdev 内存1 struct cdev *cdev_alloc(void) 2 3 struct cdev *p=kmalloc(sizeof(struct cdev),GFP_KERNEL); /*分配 cdev 的内存*/ 4 if (p) 5 memset(p, 0, sizeof(struct cdev); 6 p-kobj.ktype = 7 INIT_LIST_HEAD( 8 kobject_init( 9 10 return p; 11 cdev_add()函数和 cdev_del
7、()函数分别向系统添加和删除一个 cdev,完成字符设备的注册和注销。对 cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对 cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。2) 分配和释放设备号在 调用 cdev_add() 函 数 向系统注册 字符 设备 之前 , 应首先调用 register_chrdev_region()或 alloc_chrdev_region()函数向系统申请设备号。register_chrdev_region() 函 数 用 于 已 知 起 始 设 备的 设备 号 的 情 况; 而 alloc_chrdev_region()
8、用于设备号未知,向系统动态申请未被占用的设备号的情况,相反地 ,在 调用 cdev_del() 函 数 从系 统 注销 字符设备 之 后,unregister_chrdev_region()应该被调用以释放原先申请的设备号。3)file_operations 结构体1 struct file_operations 2 3 struct module *owner; / 拥有该结构的模块的指针,一般为 THIS_MODULES 5 loff_t(*llseek)(struct file *, loff_t, int); / 用来修改文件当前的读写位置 7 ssize_t(*read)(struc
9、t file *, char _ _user *, size_t, loff_t*); / 从设备中同步读取数据 9 ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t); / 初始化一个异步的读取操作11 ssize_t(*write)(struct file *, const char _ _user *, size_t, loff_t*); / 向设备发送数据 13 ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t
10、); / 初始化一个异步的写入操作 15 int(*readdir)(struct file *, void *, filldir_t); / 仅用于读取目录,对于设备文件,该字段为 NULL 17 unsigned int(*poll)(struct file *, struct poll_table_struct*); / 轮询函数,判断目前是否可以进行非阻塞的读取或写入 19 int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned long); / 执行设备I/O 控制命令 21 long(*unlocked_i
11、octl)(struct file *, unsigned int, unsigned long); / 不使用 BLK文件系统,将使用此种函数指针代替 ioctl 23 long(*compat_ioctl)(struct file *, unsigned int, unsigned long); / 在 64 位系统上,32 位的 ioctl 调用将使用此函数指针代替 25 int(*mmap)(struct file *, struct vm_area_struct*); / 用于请求将设备内存映射到进程地址空间 27 int(*open)(struct inode *, struct
12、file*); / 打开 29 int(*flush)(struct file*); 30 int(*release)(struct inode *, struct file*); / 关闭 32 int(*synch)(struct file *, struct dentry *, int datasync); / 刷新待处理的数据 34 int(*aio_fsync)(struct kiocb *, int datasync); / 异步 fsync 36 int(*fasync)(int, struct file *, int); / 通知设备 FASYNC 标志发生变化 38 int(
13、*lock)(struct file *, int, struct file_lock*); 39 ssize_t(*readv)(struct file *, const struct iovec *, unsigned long, loff_t*); 40 ssize_t(*writev)(struct file *, const struct iovec *, unsigned long, loff_t*); / readv 和 writev:分散/聚集型的读写操作 42 ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_a
14、ctor_t, void*); / 通常为NULL 44 ssize_t(*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int); / 通常为 NULL 46 unsigned long(*get_unmapped_area)(struct file *,unsigned long, unsigned long, unsigned long, unsigned long); / 在进程地址空间找到一个将底层设备中的内存段映射的位置 49 int(*check_flags)(int); / 允许模块检查传递给 fc
15、ntl(F_SETEL.)调用的标志 51 int(*dir_notify)(struct file *filp, unsigned long arg); / 仅对文件系统有效,驱动程序不必实现 53 int(*flock)(struct file *, int, struct file_lock*); 54 ;llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。write()函数向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行 wri
16、te()系统调用时,将得到-EINVAL 返回值。readdir()函数仅用于目录,设备节点不需要实现它。 ioctl()提供设备相关控制命令的实现 (既不是读操作也不是写操作) , 当调用成功时,返回给调用程序一个非负值。内核本身识别部分控制命令,而不必调用设备驱动中的ioctl()。如果设备不提供 ioctl()函数,对于内核不能识别的命令,用户进行 ioctl()系统调用时将获得-EINVAL 返回值。mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进行 mmap()系统调用时将获得-ENODEV 返回值。 这个函数对于帧缓冲等设备特别有意义。3)字符设备驱动的
17、组成A、字符设备驱动模块加载与卸载函数字符设备驱动模块加载函数中应该实现设备号的申请和 cdev 的注册, 而在卸载函数中应实现设备号的释放和 cdev的注销常见的设备结构体、模块加载和卸载函数形式如代码清单:1 /设备结构体 2 struct xxx_dev_t 3 4 struct cdev cdev; 5 . 6 xxx_dev; 7 /设备驱动模块加载函数 8 static int _ _init xxx_init(void) 9 10 . 11 cdev_init( /初始化 cdev 12 xxx_dev.cdev.owner = THIS_MODULE; /获取字符设备号 14
18、if (xxx_major) 15 16 register_chrdev_region(xxx_dev_no, 1, DEV_NAME);17 18 else 19 20 alloc_chrdev_region( 21 22 23 ret = cdev_add( /注册设备 24 . 25 26 /设备驱动模块卸载函数 27 static void _ _exit xxx_exit(void) 28 29 unregister_chrdev_region(xxx_dev_no, 1); /释放占用的设备号 30 cdev_del( /注销设备 31 . 32 B、字符设备驱动的 file_op
19、erations 结构体中成员函数file_operations 结构体中成员函数是字符设备驱动与内核的接口,是用户空间对 Linux 进行系统调用最终的落实者。大多数字符设备驱动会实现 read()、 write()和 ioctl()函数,常见的字符设备驱动的这 3 个函数的形式如代码清单1 /* 读设备*/ 2 ssize_t xxx_read(struct file *filp, char _ _user *buf, size_t count, loff_t*f_pos) 4 5 . 6 copy_to_user(buf, ., .); 7 . 8 设备驱动的读函数中,filp 是文件结
20、构体指针,buf 是用户空间内存的地址,该地址在内核空间不能直接读写,count 是要读的字节数,f_pos 是读的位置相对于文件开头的偏移。9 /* 写设备*/ 10 ssize_t xxx_write(struct file *filp, const char _ _user *buf, size_t count, loff_t *f_pos) 12 13 . 14 copy_from_user(., buf, .); 15 . 16 设备驱动的写函数中,filp 是文件结构体指针,buf 是用户空间内存的地址,该地址在内核空间不能直接读写,count 是要写的字节数,f_pos 是写的位
21、置相对于文件开头的偏移17 /* ioctl 函数 */ 18 int xxx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) 20 21 . 22 switch (cmd) 23 24 case XXX_CMD1: 25 . 26 break; 27 case XXX_CMD2: 28 . 29 break; 30 default: /* 不能支持的命令 */ 32 return - ENOTTY; 33 34 return 0; 35 I/O 控制函数的 cmd 参数为事先
22、定义的 I/O 控制命令, 而 arg 为对应于该命令的参数。例如对于串行设备,如果 SET_BAUDRATE 是一个设置波特率的命令,那后面的 arg 就应该是波特率值。读和写函数中的_ _user 是一个宏,表明其后的指针指向用户空间在字符设备驱动中,需要定义一个 file_operations 的实例,并将具体设备驱动的函数赋值给 file_operations 的成员,如代码清单:1 struct file_operations xxx_fops = 2 3 .owner = THIS_MODULE, 4 .read = xxx_read, 5 .write = xxx_write, 6 .ioctl = xxx_ioctl, 7 . 8 ;