linux底层驱动内核,Linux底层驱动开发需要学习哪些内容Linux底层驱动开发需要学习哪些内容想必这是很多学习Linux的朋友⼗分头疼的问题,今天就让我来告诉⼤家我们到底该学习哪些内容呢?
1. 要会⼀些硬件知识,⽐如Arm接⼝编程
2. 学会写简单的makefile
3. 编⼀应⽤程序,可以⽤makefile跑起来
4. 学会写驱动的makefile
5. 写⼀简单char驱动,makefile编译通过,可以insmod, lsmod, rmmod. 在驱动的init函数⾥打印hello world, insmod后应该能够通过dmesg看到输出。
6. 写⼀完整驱动, 加上read, write, ioctl, polling等各种函数的驱动实现。 在ioctl⾥完成从⽤户空间向内核空间传递结构体的实现。
7. 写⼀block驱动, 加上read,write,ioctl,poll等各种函数实现。
8. 简单学习下内存管理, 这个是难的,明⽩各种memory alloc的函数实现细节。这是Linux开发的基本功。
9. 学习锁机制的应⽤,这个不是难的但是容易犯错的,涉及到很多同步和并发的问题。
10. 看内核中实际应⽤的驱动代码。 你会发现基本的你已经知道了, ⼤的框架都是⼀样的, ⽆⾮是read, write, ioctl等函数的实现,但⾥⾯包含了很多很多细⼩的实现细节是之前不知道的。 这时候就要考虑到很多别的问题⽽不仅仅是基本功能的实现。 推荐您看2.6.20中integrated的⼀个驱动 kvm, 记得是在driver/lguest下,很好玩的, 就是Linux下的虚拟机驱动, 代码不长,但功能强⼤。有能⼒的可以⾃⼰写⼀操作系统按照要求做成磁盘镜像加载到虚拟机中, 然后客户机可以有⾃⼰的4G虚拟地址空间。
11. 看完驱动欢迎您进⼊Linux kernel学习中来。 简单的⽅法,跟着ldd(Linux devive driver)做⼀遍。
1、 Makefile 是如何编写
eg:
# 这是上⾯那个程序的 Makefile ⽂件
main:main.o mytool1.o mytool2.o
gcc -o main main.o mytool1.o mytool2.o
main.o:main.c mytool1.h mytool2.h
gcc -c main.c
mytool1.o:mytool1.c mytool1.h
gcc -c mytool1.c
mytool2.o:mytool2.c mytool2.h
gcc -c mytool2.c
分析:
在 Makefile 中也#开始的⾏都是注释⾏.Makefile 中重要的是描述⽂件的依赖关系的说
明.⼀般的格式是: Linux 操作系统 C 语⾔编程⼊门
target: components //表⽰的是依赖关系
TAB rule //规则
main:main.o mytool1.o mytool2.o 表⽰我们的⽬标(target)main 的依赖对象(components)是 main.o mytool1.o mytool2.o 当倚赖的对象在⽬标修改后修改的话,就要去执⾏规则⼀⾏所指定的命令.就象我们的上
⾯那个 Makefile 第3⾏所说的⼀样要执⾏ gcc -o main main.o mytool1.o mytool2.o
(注意规则⼀⾏中的 TAB表⽰那⾥是⼀个 TAB 键)
Makefile 有三个⾮常有⽤的变量.分别是$@,$^,$
$@--⽬标⽂件; $^--所有的依赖⽂件; $
1、 字符设备驱动
Linux字符设备驱动的关键数据结构cdev及file_operations结构体的操作⽅法,并分析了Linux字符设备的整体结构,给出了简单的设计模板.
2.1、驱动结构
1) cdev结构体(cdev结构体描述字符设备)
定义:
1 struct cdev {
3 struct kobject kobj; /* 内嵌的kobject对象 */
4 struct module *owner; /*所属模块*/
5 struct file_operations *ops; /*⽂件操作结构体*/
6 struct list_head list;
7 dev_t dev; /*设备号*/ 定义了设备号
8 unsigned int count;
9 };
dev_t 成员定义了设备号,为 32 位,其中⾼ 12 位为主设备号,低20位为次设备号。使⽤下列宏可以从dev_t获得主设备号和次设备号:
MAJOR(dev_t dev) //主设备号
MINOR(dev_t dev) //次设备号
⽽使⽤下列宏则可以通过主设备号和设备号⽣成 dev_t:
MKDEV(int major, int minor)
file_operations 定义了字符设备驱动提供给虚拟⽂件系统的接⼝函数
Linux 2.6内核提供了⼀组函数⽤于操作 cdev结构体
Void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
cdev_init()函数⽤于初始化 cdev 的成员,并建⽴ cdev 和 file_operations 之间的连接。
linux内核设计与实现 pdf
1 void cdev_init(struct cdev *cdev, struct file_operations *fops)
2 {
3 memset(cdev, 0, sizeof *cdev);
4 INIT_LIST_HEAD(&cdev->list);
5 cdev->kobj.ktype = &ktype_cdev_default;
6 kobject_init(&cdev->kobj);
7 cdev->ops = fops; /*将传⼊的⽂件操作结构体指针赋值给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 = &ktype_cdev_dynamic;
7 INIT_LIST_HEAD(&p->list);
8 kobject_init(&p->kobj);
9 }
10 return p;
11 }
cdev_add()函数和 cdev_del()函数分别向系统添加和删除⼀个cdev,完成字符设备的注册和注销。对 cdev_add()的调⽤通常发⽣在字符设备驱动模块加载函数中,⽽对cdev_del()函数的调⽤则通常发⽣在字符设备驱动模块卸载函数中。
2) 分配和释放设备号
在 调⽤ cdev_add() 函 数 向系统注册 字符 设备 之前 , 应⾸先调⽤register_chrdev_region()或 alloc_chrdev_region()函数向系统申请设备号。register_chrdev_region() 函 数 ⽤ 于 已 知 起 始 设 备的 设备 号 的 情 况; ⽽alloc_chrdev_region()⽤于设备号未知,向系统动态申请未被占⽤的设备号的情况,相反地 ,在 调⽤ 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)(struct 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); // 初始化⼀个异步的写⼊操作
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_ioctl)(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 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(*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_actor_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); // 允许模块检查传递给fcntl()调⽤的标志
51 int(*dir_notify)(struct file *filp, unsigned long arg); // 仅对⽂件系统有效,驱动程序不必实现
53 int(*flock)(struct file *, int, struct file_lock*);
54 };
llseek()函数⽤来修改⼀个⽂件的当前读写位置,并将新位置返回,在出错时,这个函数返回⼀个负值
read()函数⽤来从设备中读取数据,成功时函数返回读取的字节数,出错时返回⼀个负值。
write()函数向设备发送数据,成功时该函数返回写⼊的字节数。如果此函数未被实现,当⽤户进⾏write()系统调⽤时,将得到-EINVAL返回值。
readdir()函数仅⽤于⽬录,设备节点不需要实现它。
ioctl()提供设备相关控制命令的实现 (既不是读操作也不是写操作) , 当调⽤成功时,返回给调⽤程序⼀个⾮负值。内核本⾝识别部分控制命令,⽽不必调⽤设备驱动中的
ioctl()。如果设备不提供ioctl()函数,对于内核不能识别的命令,⽤户进⾏ioctl()系统调⽤时将获得-EINVAL返回值。
mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,⽤户进⾏ mmap()系统调⽤时将获得-ENODEV返回值。 这个函数对于帧缓冲等设备特别有意义。
3)字符设备驱动的组成
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(&xxx_dev.cdev, &xxx_fops); //初始化cdev
12 xxx_dev.cdev.owner = THIS_MODULE; //获取字符设备号
14 if (xxx_major)
15 {
16 register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
17 }
18 else
19 {
20 alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
21 }
22
23 ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); //注册设备
24 ...
25 }
26 //设备驱动模块卸载函数
27 static void _ _exit xxx_exit(void)
28 {
29 unregister_chrdev_region(xxx_dev_no, 1); //释放占⽤的设备号
30 cdev_del(&xxx_dev.cdev); //注销设备
31 ...
32 }
B、字符设备驱动的file_operations 结构体中成员函数
file_operations 结构体中成员函数是字符设备驱动与内核的接⼝,是⽤户空间对Linux进⾏系统调⽤终的落实者。 ⼤多数字符设备驱动会实现read()、 write()和 ioctl()函数,常见的字符设备驱动的这3个函数的形式如代码清单
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论