当前位置:   article > 正文

Linux内核及驱动开发学习笔记(基于Exynos4412)

Linux内核及驱动开发学习笔记(基于Exynos4412)

向内核添加新功能

静态加载法

即新功能源码与内核其它代码一起编译进uImage文件内

动态加载法

即新功能源码与内核其它源码不一起编译,而是独立编译成内核的插件(被称为内核模块)文件.ko


模块传参

module_param(name, type, perm); // 将指定的全局变量设置成模块参数

name:全局变量
type
    使用符号      实际类型                传参方式
    bool                bool                     insmod xxx.ko  变量名=0 或 1
    invbool           bool                     insmod xxx.ko  变量名=0 或 1
    charp             char *                   insmod xxx.ko  变量名="字符串内容"
    short              short                     insmod xxx.ko  变量名=数值
    int                    int                        insmod xxx.ko  变量名=数值
    long                long                      insmod xxx.ko  变量名=数值
    ushort         unsigned short         insmod xxx.ko  变量名=数值
    uint             unsigned int             insmod xxx.ko  变量名=数值
    ulong          unsigned long          insmod xxx.ko  变量名=数值
perm:给对应文件 /sys/module/name/parameters/变量名 指定操作权限
    #define S_IRWXU 00700
    #define S_IRUSR 00400
    #define S_IWUSR 00200
    #define S_IXUSR 00100
    #define S_IRWXG 00070
    #define S_IRGRP 00040
    #define S_IWGRP 00020
    #define S_IXGRP 00010
    #define S_IRWXO 00007
    #define S_IROTH 00004
    #define S_IWOTH 00002  //不要用 编译出错
    #define S_IXOTH 00001

module_param_array(name, type, &num, perm);

name、type、perm同module_param,type指数组中元素的类型
&num:存放数组大小变量的地址,可以填NULL(确保传参个数不越界)
传参方式 insmod xxx.ko  数组名=元素值0,元素值1,...元素值num-1  

示例代码

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. int gx = 10;
  4. char* gstr = "hello";
  5. int garr[5] = {1, 2, 3, 4, 5};
  6. module_param(gx, int, 0664);
  7. module_param(gstr, charp, 0664);
  8. module_param_array(garr, int, NULL, 0664);
  9. int __init testparam_init(void)
  10. {
  11. int i = 0;
  12. printk("gx = %d\n", gx);
  13. printk("gstr = %s\n", gstr);
  14. for (i = 0; i < 5; i++) {
  15. printk("%d ", garr[i]);
  16. }
  17. printk("\n");
  18. return 0;
  19. }
  20. void __exit testparam_exit(void)
  21. {
  22. printk("testparam will exit\n");
  23. }
  24. MODULE_LICENSE("GPL");
  25. module_init(testparam_init);
  26. module_exit(testparam_exit);

插ko:

  1. shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./testparam.ko
  2. shrek@ubuntu16:~/share/mydrivercode$ dmesg
  3. [ 1167.269738] gx = 10
  4. [ 1167.269739] gstr = hello
  5. [ 1167.269739] 1
  6. [ 1167.269739] 2
  7. [ 1167.269740] 3
  8. [ 1167.269740] 4
  9. [ 1167.269740] 5
  1. shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./testparam.ko gx=1000 gstr="hi" garr=5,6,7,8,9
  2. shrek@ubuntu16:~/share/mydrivercode$ sudo dmesg
  3. [ 1145.753620] gx = 1000
  4. [ 1145.753621] gstr = hi
  5. [ 1145.753622] 5
  6. [ 1145.753622] 6
  7. [ 1145.753622] 7
  8. [ 1145.753622] 8
  9. [ 1145.753622] 9

模块依赖

既然内核模块的代码与其它内核代码共用统一的运行环境,也就是说模块只是存在形式上独立,运行上其实和内核其它源码是一个整体,它们隶属于同一个程序,因此一个模块或内核其它部分源码应该可以使用另一个模块的一些全局特性。

一个模块中这些可以被其它地方使用的名称被称为导出符号,所有导出符号被填在同一个表中这个表被称为符号表

最常用的可导出全局特性为全局变量函数

查看符号表的命令:nm
nm查看elf格式的可执行文件或目标文件中包含的符号表,用法:

nm  文件名  (可以通过man nm查看一些字母含义)

两个用于导出模块中符号名称的宏:

EXPORT_SYMBOL(函数名或全局变量名)
EXPORT_SYMBOL_GPL(函数名或全局变量名)   需要GPL许可证协议验证

使用导出符号的地方,需要对这些符号进行extern声明后才能使用这些符号

B模块使用了A模块导出的符号,此时称B模块依赖于A模块,则:

1. 编译次序:先编译模块A,再编译模块B,当两个模块源码在不同目录时,需要:i. 先编译导出符号的模块A ii. 拷贝A模块目录中的Module.symvers到B模块目录 iii. 编译使用符号的模块B。否则编译B模块时有符号未定义错误
2. 加载次序:先插入A模块,再插入B模块,否则B模块插入失败
3. 卸载次序:先卸载B模块,在卸载A模块,否则A模块卸载失败

补充说明:
内核符号表(直接当文本文件查看)

运行时可查看 /proc/kallsyms
编译后可查看 /boot/System.map

示例代码

moduleA.c:

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. int gx = 10;
  4. EXPORT_SYMBOL(gx);
  5. int __init modulea_init(void)
  6. {
  7. printk("In moduleA init, gx = %d\n", gx);
  8. return 0;
  9. }
  10. void __exit modulea_exit(void)
  11. {
  12. printk("modulea will exit\n");
  13. }
  14. MODULE_LICENSE("GPL");
  15. module_init(modulea_init);
  16. module_exit(modulea_exit);

moduleB.c:

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. extern int gx;
  4. int __init moduleb_init(void)
  5. {
  6. printk("In moduleB init, gx = %d\n", gx);
  7. return 0;
  8. }
  9. void __exit moduleb_exit(void)
  10. {
  11. printk("moduleb will exit\n");
  12. }
  13. MODULE_LICENSE("GPL");
  14. module_init(moduleb_init);
  15. module_exit(moduleb_exit);

Makefile(注意编译顺序):

  1. ifeq ($(KERNELRELEASE),)
  2. ifeq ($(ARCH),arm)
  3. KERNELDIR ?= /home/shrek/Linux_4412/kernel/linux-3.14
  4. ROOTFS ?= /opt/4412/rootfs
  5. else
  6. KERNELDIR ?= /lib/modules/$(shell uname -r)/build
  7. endif
  8. PWD := $(shell pwd)
  9. modules:
  10. $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
  11. modules_install:
  12. $(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install
  13. clean:
  14. rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
  15. else
  16. obj-m += moduleA.o
  17. obj-m += moduleB.o
  18. endif

插ko(注意插入顺序和卸载顺序):

  1. shrek@ubuntu16:~/share/mydrivercode/twomodules$ sudo insmod ./moduleA.ko
  2. shrek@ubuntu16:~/share/mydrivercode/twomodules$ sudo insmod ./moduleB.ko
  3. shrek@ubuntu16:~/share/mydrivercode/twomodules$ sudo dmesg
  4. [ 8145.865709] In moduleA init, gx = 10
  5. [ 8147.510935] In moduleB init, gx = 10
  6. shrek@ubuntu16:~/share/mydrivercode/twomodules$ sudo rmmod moduleB
  7. shrek@ubuntu16:~/share/mydrivercode/twomodules$ sudo rmmod moduleA

内核空间和用户空间

为了彻底解决一个应用程序出错不影响系统和其它app的运行,操作系统给每个app一个独立的假想的地址空间,这个假想的地址空间被称为虚拟地址空间(也叫逻辑地址),操作系统也占用其中固定的一部分,32位Linux的虚拟地址空间大小为4G,并将其划分两部分:

1. 0~3G 用户空间 :每个应用程序只能使用自己的这份虚拟地址空间

2. 3G~4G 内核空间:内核使用的虚拟地址空间,应用程序不能直接使用这份地址空间,但可以通过一些系统调用函数与其中的某些空间进行数据通信

实际内存操作时,需要将虚拟地址映射到实际内存的物理地址,然后才进行实际的内存读写


执行流

有开始有结束总体顺序执行的一段独立代码,又被称为代码上下文 

计算机系统中的执行流的分类:

任务流--任务上下文(都参与CPU时间片轮转,都有任务五状态:就绪态  运行态  睡眠态  僵死态  暂停态)
   1.  进程
   2.  线程
       内核线程:内核创建的线程
       应用线程:应用进程创建的线程

异常流--异常上下文
   1. 中断
   2. 其它异常

应用编程可能涉及到的执行流:

1. 进程
2. 线程     

内核编程可能涉及到的执行流:  

1. 应用程序自身代码运行在用户空间,处于用户态   -----------------  用户态app
2. 应用程序正在调用系统调用函数,运行在内核空间,处于内核态,即代码是内核代码但处于应用执行流(即属于一个应用进程或应用线程) -----------------  内核态app
3. 一直运行于内核空间,处于内核态,属于内核内的任务上下文 ----------------- 内核线程
4. 一直运行于内核空间,处于内核态,专门用来处理各种异常 ----------------- 异常上下文


模块编程与应用编程的比较

不同点内核模块应用程序
API来源不能使用任何库函数各种库函数均可以使用
运行空间内核空间用户空间
运行权限特权模式运行非特权模式运行
编译方式静态编译进内核镜像或编译特殊的ko文件elf格式的应用程序可执行文件
运行方式模块中的函数在需要时被动调用从main开始顺序执行
入口函数init_modulemain
退出方式cleanup_modulemain函数返回或调用exit
浮点支持一般不涉及浮点运算,因此printk不支持浮点数据支持浮点运算,printf可以打印浮点数据
并发考虑需要考虑多种执行流并发的竞态情况只需考虑多任务并行的竞态
程序出错可能会导致整个系统崩溃只会让自己崩溃

内核接口头文件查询

大部分API函数包含的头文件在include/linux目录下,因此:

1. 首先在 include/linux 查询指定函数:grep  名称  ./   -r   -n
2. 找不到则更大范围的 include 目录下查询,命令同上


字符设备驱动

linux的文件种类:

1. -:普通文件
2. d:目录文件
3. p:管道文件
4. s:本地socket文件
5. l:链接文件
6. c:字符设备
7. b:块设备


Linux内核按驱动程序实现模型框架的不同,将设备分为三类:

1. 字符设备:按字节流形式进行数据读写的设备,一般情况下按顺序访问,数据量不大,一般不设缓存
2. 块设备:按整块进行数据读写的设备,最小的块大小为512字节(一个扇区),块的大小必须是扇区的整数倍,Linux系统的块大小一般为4096字节,随机访问,设缓存以提高效率
3. 网络设备:针对网络数据收发的设备


设备号

内核中同类设备的区分

内核用设备号来区分同类里不同的设备,设备号是一个无符号32位整数,数据类型为dev_t,设备号分为两部分:

1. 主设备号:占高12位,用来表示驱动程序相同的一类设备
2. 次设备号:占低20位,用来表示被操作的哪个具体设备

应用程序打开一个设备文件时,通过设备号来查找定位内核中管理的设备。

MKDEV宏用来将主设备号和次设备号组合成32位完整的设备号,用法:

  1. dev_t devno;
  2. int major = 251; // 主设备号
  3. int minor = 2; // 次设备号
  4. devno = MKDEV(major,minor);

MAJOR宏用来从32位设备号中分离出主设备号,用法:

  1. dev_t devno = MKDEV(249,1);
  2. int major = MAJOR(devno);

MINOR宏用来从32位设备号中分离出次设备号,用法:

  1. dev_t devno = MKDEV(249,1);
  2. int minor = MINOR(devno);

如果已知一个设备的主次设备号,应用层指定好设备文件名,那么可以用mknod命令在/dev目录创建代表这个设备的文件,即此后应用程序对此文件的操作就是对其代表的设备操作,mknod用法如下:

  1. @ cd /dev
  2. @ mknod 设备文件名 设备种类(c为字符设备,b为块设备)  主设备号  次设备号    //ubuntu下需加sudo执行

在应用程序中如果要创建设备可以调用系统调用函数mknod,其原型如下:

int mknod(const char *pathname, mode_t mode, dev_t dev);

pathname:带路径的设备文件名,无路径默认为当前目录,一般都创建在/dev下
mode:文件权限 位或 S_IFCHR/S_IFBLK
dev:32位设备号
返回值:成功为0,失败-1


申请和注销设备号

字符驱动开发的第一步是通过模块的入口函数向内核添加本设备驱动的代码框架,主要完成:

1. 申请设备号
2. 定义、初始化、向内核添加代表本设备的结构体元素

int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:手动分配设备号,先验证设备号是否被占用,如果没有则申请占用该设备号
参数:
    from:自己指定的设备号
    count:申请的设备数量
    name:/proc/devices文件中与该设备对应的名字,方便用户层查询主设备号
返回值:
    成功为0,失败负数,绝对值为错误码


int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
功能:动态分配设备号,查询内核里未被占用的设备号,如果找到则占用该设备号
参数:
    dev:分配设备号成功后用来存放分配到的设备号
    baseminior:起始的次设备号,一般为0
    count:申请的设备数量
    name:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号
返回值:
    成功为0,失败负数,绝对值为错误码
 

分配成功后在/proc/devices 可以查看到申请到主设备号和对应的设备名,mknod时参数可以参考查到的此设备信息


void unregister_chrdev_region(dev_t from, unsigned count)
功能:释放设备号
参数:
    from:已成功分配的设备号将被释放
    count:申请成功的设备数量
 

释放后 /proc/devices 文件对应的记录消失


示例代码(申请和注销设备号)

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. int major = 11;
  5. int minor = 0;
  6. int mychar_num = 1;
  7. int __init mychar_init(void)
  8. {
  9. int ret = 0;
  10. dev_t devno = MKDEV(major, minor);
  11. ret = register_chrdev_region(devno, mychar_num, "mychar");
  12. if (ret) {
  13. ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
  14. if (ret) {
  15. printk("get devno faile\n");
  16. return -1;
  17. }
  18. major = MAJOR(devno);
  19. }
  20. return 0;
  21. }
  22. void __exit mychar_exit(void)
  23. {
  24. dev_t devno = MKDEV(major, minor);
  25. unregister_chrdev_region(devno, mychar_num);
  26. }
  27. MODULE_LICENSE("GPL");
  28. module_init(mychar_init);
  29. module_exit(mychar_exit);

插ko之后可通过 /proc/devices 查看:

  1. shrek@ubuntu16:~/share/mydrivercode$ cat /proc/devices | grep mychar
  2. 11 mychar

注册字符设备

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

cdev结构体

  1. struct cdev
  2. {
  3.     struct kobject kobj;//表示该类型实体是一种内核对象
  4.     struct module *owner;//填THIS_MODULE,表示该字符设备从属于哪个内核模块
  5.     const struct file_operations *ops;//指向空间存放着针对该设备的各种操作函数地址
  6.     struct list_head list;//链表指针域
  7.     dev_t dev;//设备号
  8.     unsigned int count;//设备数量
  9. };

1. 直接定义:定义结构体全局变量

2. 动态申请:struct  cdev * cdev_alloc()

file_operations结构体

  1. struct file_operations 
  2. {
  3.    struct module *owner;           //填THIS_MODULE,表示该结构体对象从属于哪个内核模块
  4.    int (*open) (struct inode *, struct file *);    //打开设备
  5.    int (*release) (struct inode *, struct file *);    //关闭设备
  6.    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);    //读设备
  7.    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);    //写设备
  8.    loff_t (*llseek) (struct file *, loff_t, int);        //定位
  9.    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
  10.    unsigned int (*poll) (struct file *, struct poll_table_struct *);    //POLL机制,实现多路复用的支持
  11.    int (*mmap) (struct file *, struct vm_area_struct *); //映射内核空间到用户层
  12.    int (*fasync) (int, struct file *, int); //信号驱动
  13.    //......
  14. };

一般定义一个struct file_operations类型的全局变量并用自己实现各种操作函数名对其进行初始化


int cdev_add(struct cdev *p, dev_t dev, unsigned int count)
功能:将指定字符设备添加到内核
参数:
    p:指向被添加的设备
    dev:设备号
    count:设备数量,一般填1


void cdev_del(struct cdev *p)
功能:从内核中移除一个字符设备
参数:
    p:指向被移除的字符设备


字符设备驱动开发步骤:

1.  如果设备有自己的一些控制数据,则定义一个包含struct cdev cdev成员的结构体struct mydev,其它成员根据设备需求,设备简单则直接用struct cdev
2.  定义一个struct mydev或struct cdev的全局变量来表示本设备;也可以定义一个struct mydev或struct cdev的全局指针(记得在init时动态分配)
3.  定义三个全局变量分别来表示主设备号、次设备号、设备数
4.  定义一个struct file_operations结构体变量,其owner成员置成THIS_MODULE
5.  module init函数流程:a. 申请设备号 b. 如果是全局设备指针则动态分配代表本设备的结构体元素 c. 初始化struct cdev成员 d. 设置struct cdev的owner成员为THIS_MODULE  e. 添加字符设备到内核
6.  module exit函数:a. 注销设备号 b. 从内核中移除struct cdev  c. 如果如果是全局设备指针则释放其指向空间
7.  编写各个操作函数并将函数名初始化给struct file_operations结构体变量

验证操作步骤:

1. 编写驱动代码mychar.c
2. make生成ko文件
3. insmod内核模块
4. 查阅字符设备用到的设备号(主设备号):cat  /proc/devices  |  grep  申请设备号时用的名字
5. 创建设备文件(设备节点) : mknod   /dev/???   c   上一步查询到的主设备号    代码中指定初始次设备号
6. 编写app验证驱动(testmychar_app.c)
7. 编译运行app,dmesg命令查看内核打印信息


示例代码(注册字符设备)

mychar.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. int major = 11;
  6. int minor = 0;
  7. int mychar_num = 1;
  8. struct cdev mydev;
  9. int mychar_open(struct inode* pnode, struct file* pfile)
  10. {
  11. printk("mychar_open is called\n");
  12. return 0;
  13. }
  14. int mychar_close(struct inode* pnode, struct file* pfile)
  15. {
  16. printk("mychar_close is called\n");
  17. return 0;
  18. }
  19. struct file_operations myops = {
  20. .owner = THIS_MODULE,
  21. .open = mychar_open,
  22. .release = mychar_close,
  23. };
  24. int __init mychar_init(void)
  25. {
  26. int ret = 0;
  27. dev_t devno = MKDEV(major, minor);
  28. /* 申请设备号 */
  29. ret = register_chrdev_region(devno, mychar_num, "mychar");
  30. if (ret) {
  31. ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
  32. if (ret) {
  33. printk("get devno failed\n");
  34. return -1;
  35. }
  36. major = MAJOR(devno);
  37. }
  38. /* 给struct cdev对象指定操作函数集 */
  39. cdev_init(&mydev, &myops);
  40. /* 给struct cdev对象添加到内核对应的数据结构里 */
  41. mydev.owner = THIS_MODULE;
  42. cdev_add(&mydev, devno, mychar_num);
  43. return 0;
  44. }
  45. void __exit mychar_exit(void)
  46. {
  47. dev_t devno = MKDEV(major, minor);
  48. cdev_del(&mydev);
  49. unregister_chrdev_region(devno, mychar_num);
  50. }
  51. MODULE_LICENSE("GPL");
  52. module_init(mychar_init);
  53. module_exit(mychar_exit);

test.c

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. int main(int argc, char* argv[])
  7. {
  8. int fd = -1;
  9. if (argc < 2) {
  10. printf("The argument is too few\n");
  11. return 1;
  12. }
  13. fd = open(argv[1], O_RDONLY);
  14. if (fd < 0) {
  15. printf("open %s failed\n", argv[1]);
  16. return 2;
  17. }
  18. close(fd);
  19. fd = -1;
  20. return 0;
  21. }

测试结果

  1. shrek@ubuntu16:~/share/mydrivercode$ sudo mknod /dev/mydev c 11 0
  2. shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./mychar.ko
  3. shrek@ubuntu16:~/share/mydrivercode$ ./a.out /dev/mydev
  4. shrek@ubuntu16:~/share/mydrivercode$ dmesg
  5. [11215.776355] mychar_open is called
  6. [11215.776358] mychar_close is called

两个操作函数中常用的结构体

内核中记录文件元信息的结构体

  1. struct inode
  2. {
  3.     //....
  4.     dev_t  i_rdev; // 设备号
  5.     struct cdev  *i_cdev; // 如果是字符设备才有此成员,指向对应设备驱动程序中的加入系统的struct cdev对象
  6.     //....
  7. }
  8. /*
  9.     1. 内核中每个该结构体对象对应着一个实际文件,一对一
  10.     2. open一个文件时如果内核中该文件对应的inode对象已存在则不再创建,不存在才创建
  11.     3. 内核中用此类型对象关联到对此文件的操作函数集(对设备而言就是关联到具体驱动代码)
  12. */

读写文件内容过程中用到的一些控制性数据组合而成的对象------文件操作引擎(文件操控器)

  1. struct file
  2. {
  3.     //...
  4.     mode_t f_mode; // 不同用户的操作权限,驱动一般不用
  5.     loff_t f_pos; // position 数据位置指示器,需要控制数据开始读写位置的设备有用
  6.     unsignedint f_flags; // open时的第二个参数flags存放在此,驱动中常用
  7.     structfile_operations *f_op; // open时从struct inode中i_cdev的对应成员获得地址,驱动开发中用来协助理解工作原理,内核中使用
  8.     void *private_data; // 本次打开文件的私有数据,驱动中常来在几个操作函数间传递共用数据
  9.     structdentry *f_dentry; // 驱动中一般不用,除非需要访问对应文件的inode,用法flip->f_dentry->d_inode
  10.     int refcnt; // 引用计数,保存着该对象地址的位置个数,close时发现refcnt为0才会销毁该struct file对象
  11.     //...
  12. };
  13. /*
  14.     1. open函数被调用成功一次,则创建一个该对象,因此可以认为一个该类型的对象对应一次指定文件的操作
  15.     2. open同一个文件多次,每次open都会创建一个该类型的对象
  16.     3. 文件描述符数组中存放的地址指向该类型的对象
  17.     4. 每个文件描述符都对应一个struct file对象的地址
  18. */

常用操作函数说明

  1. int (*open) (struct inode *, struct file *); //打开设备
  2. /*
  3. 指向函数一般用来对设备进行硬件上的初始化,对于一些简单的设备该函数只需要return 0,对应open系统调用,是open系统调用函数实现过程中调用的函数,
  4. */
  5. int (*release) (struct inode *, struct file *); //关闭设备
  6. /*
  7. ,指向函数一般用来对设备进行硬件上的关闭操作,对于一些简单的设备该函数只需要return 0,对应close系统调用,是close系统调用函数实现过程中调用的函数
  8. */
  9. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //读设备
  10. /*
  11. 指向函数用来将设备产生的数据读到用户空间,对应read系统调用,是read系统调用函数实现过程中调用的函数
  12. */
  13. ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //写设备
  14. /*
  15. 指向函数用来将用户空间的数据写进设备,对应write系统调用,是write系统调用函数实现过程中调用的函数
  16. */
  17. loff_t (*llseek) (struct file *, loff_t, int); //数据操作位置的定位
  18. /*
  19. 指向函数用来获取或设置设备数据的开始操作位置(位置指示器),对应lseek系统调用,是lseek系统调用函数实现过程中调用的函数
  20. */
  21. long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
  22. /*
  23. 指向函数用来获取、设置设备一些属性或设备的工作方式等非数据读写操作,对应ioctl系统调用,是ioctl系统调用函数实现过程中调用的函数
  24. */
  25. unsigned int (*poll) (struct file *, struct poll_table_struct *);//POLL机制,实现对设备的多路复用方式的访问
  26. /*
  27. 指向函数用来协助多路复用机制完成对本设备可读、可写数据的监控,对应select、poll、epoll_wait系统调用,是select、poll、epoll_wait系统调用函数实现过程中调用的函数
  28. */
  29. int (*fasync) (int, struct file *, int); //信号驱动
  30. /*
  31. 指向函数用来创建信号驱动机制的引擎,对应fcntl系统调用的FASYNC标记设置,是fcntl系统调用函数FASYNC标记设置过程中调用的函数
  32. */

读写操作实现

ssize_t xxx_read(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos);
完成功能:读取设备产生的数据
参数:
    filp:指向open产生的struct file类型的对象,表示本次read对应的那次open
    pbuf:指向用户空间一块内存,用来保存读到的数据
    count:用户期望读取的字节数
    ppos:对于需要位置指示器控制的设备操作有用,用来指示读取的起始位置,读完后也需要变更位置指示器的指示位置
 返回值:
    本次成功读取的字节数,失败返回-1

内核空间拷贝到用户空间
put_user(x, ptr) // 宏
x:char、int类型的简单变量名

unsigned long copy_to_user (void __user * to, const void * from, unsigned long n)
成功为返回0,失败非0


ssize_t xxx_write (struct file *filp, const char __user *pbuf, size_t count, loff_t *ppos);  
完成功能:向设备写入数据
参数:
    filp:指向open产生的struct file类型的对象,表示本次write对应的那次open
    pbuf:指向用户空间一块内存,用来保存被写的数据
    count:用户期望写入的字节数
    ppos:对于需要位置指示器控制的设备操作有用,用来指示写入的起始位置,写完后也需要变更位置指示器的指示位置
 返回值:
    本次成功写入的字节数,失败返回-1

用户空间拷贝到内核空间
get_user(x, ptr) // 宏
x:char、int类型的简单变量名

unsigned long copy_from_user (void * to, const void __user * from, unsigned long n)
成功为返回0,失败非0


示例代码(读写操作)

mychar.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/uaccess.h>
  6. #define BUF_LEN 100
  7. int major = 11;
  8. int minor = 0;
  9. int mychar_num = 1;
  10. struct cdev mydev;
  11. char mydev_buf[BUF_LEN];
  12. int curlen = 0;
  13. int mychar_open(struct inode* pnode, struct file* pfile)
  14. {
  15. printk("mychar_open is called\n");
  16. return 0;
  17. }
  18. int mychar_close(struct inode* pnode, struct file* pfile)
  19. {
  20. printk("mychar_close is called\n");
  21. return 0;
  22. }
  23. ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
  24. {
  25. int size = 0;
  26. int ret = 0;
  27. if (count > curlen) {
  28. size = curlen;
  29. } else {
  30. size = count;
  31. }
  32. ret = copy_to_user(puser, mydev_buf, size);
  33. if (ret) {
  34. printk("copy_to_user failed\n");
  35. return -1;
  36. }
  37. memcpy(mydev_buf, mydev_buf + size, curlen - size); // 将未读走的数据前移至buf头
  38. curlen = curlen - size;
  39. return size;
  40. }
  41. ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
  42. {
  43. int size = 0;
  44. int ret = 0;
  45. if (count > BUF_LEN - curlen) {
  46. size = BUF_LEN - curlen;
  47. } else {
  48. size = count;
  49. }
  50. ret = copy_from_user(mydev_buf + curlen, puser, size);
  51. if (ret) {
  52. printk("copy_from_user failed\n");
  53. return -1;
  54. }
  55. curlen = curlen + size;
  56. return size;
  57. }
  58. struct file_operations myops = {
  59. .owner = THIS_MODULE,
  60. .open = mychar_open,
  61. .release = mychar_close,
  62. .read = mychar_read,
  63. .write = mychar_write,
  64. };
  65. int __init mychar_init(void)
  66. {
  67. int ret = 0;
  68. dev_t devno = MKDEV(major, minor);
  69. /* 申请设备号 */
  70. ret = register_chrdev_region(devno, mychar_num, "mychar");
  71. if (ret) {
  72. ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
  73. if (ret) {
  74. printk("get devno failed\n");
  75. return -1;
  76. }
  77. major = MAJOR(devno);
  78. }
  79. /* 给struct cdev对象指定操作函数集 */
  80. cdev_init(&mydev, &myops);
  81. /* 给struct cdev对象添加到内核对应的数据结构里 */
  82. mydev.owner = THIS_MODULE;
  83. cdev_add(&mydev, devno, mychar_num);
  84. return 0;
  85. }
  86. void __exit mychar_exit(void)
  87. {
  88. dev_t devno = MKDEV(major, minor);
  89. cdev_del(&mydev);
  90. unregister_chrdev_region(devno, mychar_num);
  91. }
  92. MODULE_LICENSE("GPL");
  93. module_init(mychar_init);
  94. module_exit(mychar_exit);

test.c

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. int main(int argc, char* argv[])
  7. {
  8. int fd = -1;
  9. char buf[8] = "";
  10. if (argc < 2) {
  11. printf("The argument is too few\n");
  12. return 1;
  13. }
  14. fd = open(argv[1], O_RDWR);
  15. if (fd < 0) {
  16. printf("open %s failed\n", argv[1]);
  17. return 2;
  18. }
  19. write(fd, "hello", 6);
  20. read(fd, buf, 8);
  21. printf("buf = %s\n",buf);
  22. close(fd);
  23. fd = -1;
  24. return 0;
  25. }

测试结果

  1. shrek@ubuntu16:~/share/mydrivercode$ sudo mknod /dev/mydev c 11 0
  2. shrek@ubuntu16:~/share/mydrivercode$ sudo chmod a+w /dev/mydev
  3. shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./mychar.ko
  4. shrek@ubuntu16:~/share/mydrivercode$ ./a.out /dev/mydev
  5. buf = hello

避免使用全局变量的写法

已知成员的地址获得所在结构体变量的地址:container_of(成员地址, 结构体类型名, 成员在结构体中的名称)

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/uaccess.h>
  6. #define BUF_LEN 100
  7. int major = 11;
  8. int minor = 0;
  9. int mychar_num = 1;
  10. struct mychar_dev
  11. {
  12. struct cdev mydev;
  13. char mydev_buf[BUF_LEN];
  14. int curlen;
  15. };
  16. struct mychar_dev gmydev;
  17. int mychar_open(struct inode* pnode, struct file* pfile)
  18. {
  19. pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
  20. printk("mychar_open is called\n");
  21. return 0;
  22. }
  23. int mychar_close(struct inode* pnode, struct file* pfile)
  24. {
  25. printk("mychar_close is called\n");
  26. return 0;
  27. }
  28. ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
  29. {
  30. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  31. int size = 0;
  32. int ret = 0;
  33. if (count > pmydev->curlen) {
  34. size = pmydev->curlen;
  35. } else {
  36. size = count;
  37. }
  38. ret = copy_to_user(puser, pmydev->mydev_buf, size);
  39. if (ret) {
  40. printk("copy_to_user failed\n");
  41. return -1;
  42. }
  43. memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);
  44. pmydev->curlen -= size;
  45. return size;
  46. }
  47. ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
  48. {
  49. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  50. int size = 0;
  51. int ret = 0;
  52. if (count > BUF_LEN - pmydev->curlen) {
  53. size = BUF_LEN - pmydev->curlen;
  54. } else {
  55. size = count;
  56. }
  57. ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
  58. if (ret) {
  59. printk("copy_from_user failed\n");
  60. return -1;
  61. }
  62. pmydev->curlen += size;
  63. return size;
  64. }
  65. struct file_operations myops = {
  66. .owner = THIS_MODULE,
  67. .open = mychar_open,
  68. .release = mychar_close,
  69. .read = mychar_read,
  70. .write = mychar_write,
  71. };
  72. int __init mychar_init(void)
  73. {
  74. int ret = 0;
  75. dev_t devno = MKDEV(major, minor);
  76. /* 申请设备号 */
  77. ret = register_chrdev_region(devno, mychar_num, "mychar");
  78. if (ret) {
  79. ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
  80. if (ret) {
  81. printk("get devno faile\n");
  82. return -1;
  83. }
  84. major = MAJOR(devno);
  85. }
  86. /* 给struct cdev对象指定操作函数集 */
  87. cdev_init(&gmydev.mydev, &myops);
  88. /* 给struct cdev对象添加到内核对应的数据结构里 */
  89. gmydev.mydev.owner = THIS_MODULE;
  90. cdev_add(&gmydev.mydev, devno, mychar_num);
  91. return 0;
  92. }
  93. void __exit mychar_exit(void)
  94. {
  95. dev_t devno = MKDEV(major, minor);
  96. cdev_del(&gmydev.mydev);
  97. unregister_chrdev_region(devno, mychar_num);
  98. }
  99. MODULE_LICENSE("GPL");
  100. module_init(mychar_init);
  101. module_exit(mychar_exit);

ioctl操作实现

long xxx_ioctl (struct file *filp, unsigned int cmd, unsigned long arg);
功能:对相应设备做指定的控制操作(各种属性的设置获取等等)
参数:
    filp:指向open产生的struct file类型的对象,表示本次ioctl对应的那次open
    cmd:用来表示做的是哪一个操作
    arg:和cmd配合用的参数
返回值:成功为0,失败-1

cmd组成

1. dir(direction),ioctl 命令访问模式(属性数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
2. type(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如 
   ‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
3. nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
4. size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;

  1. #define _IOC(dir,type,nr,size) (((dir)<<_IOC_DIRSHIFT)| \
  2. ((type)<<_IOC_TYPESHIFT)| \
  3. ((nr)<<_IOC_NRSHIFT)| \
  4. ((size)<<_IOC_SIZESHIFT))
  5. /* used to create numbers */
  6. // 定义不带参数的 ioctl 命令
  7. #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
  8. //定义带读参数的ioctl命令(copy_to_user) size为类型名
  9. #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
  10. //定义带写参数的 ioctl 命令(copy_from_user) size为类型名
  11. #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
  12. //定义带读写参数的 ioctl 命令 size为类型名
  13. #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
  14. /* used to decode ioctl numbers */
  15. #define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
  16. #define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
  17. #define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
  18. #define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

示例代码(ioctl操作)

mychar.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/uaccess.h>
  6. #include "mychar.h"
  7. #define BUF_LEN 100
  8. int major = 11;
  9. int minor = 0;
  10. int mychar_num = 1;
  11. struct mychar_dev
  12. {
  13. struct cdev mydev;
  14. char mydev_buf[BUF_LEN];
  15. int curlen;
  16. };
  17. struct mychar_dev gmydev;
  18. int mychar_open(struct inode* pnode, struct file* pfile)
  19. {
  20. pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
  21. printk("mychar_open is called\n");
  22. return 0;
  23. }
  24. int mychar_close(struct inode* pnode, struct file* pfile)
  25. {
  26. printk("mychar_close is called\n");
  27. return 0;
  28. }
  29. ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
  30. {
  31. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  32. int size = 0;
  33. int ret = 0;
  34. if (count > pmydev->curlen) {
  35. size = pmydev->curlen;
  36. } else {
  37. size = count;
  38. }
  39. ret = copy_to_user(puser, pmydev->mydev_buf, size);
  40. if (ret) {
  41. printk("copy_to_user failed\n");
  42. return -1;
  43. }
  44. memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);
  45. pmydev->curlen -= size;
  46. return size;
  47. }
  48. ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
  49. {
  50. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  51. int size = 0;
  52. int ret = 0;
  53. if (count > BUF_LEN - pmydev->curlen) {
  54. size = BUF_LEN - pmydev->curlen;
  55. } else {
  56. size = count;
  57. }
  58. ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
  59. if (ret) {
  60. printk("copy_from_user failed\n");
  61. return -1;
  62. }
  63. pmydev->curlen += size;
  64. return size;
  65. }
  66. long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
  67. {
  68. int __user *pret = (int*)arg;
  69. int maxlen = BUF_LEN;
  70. int ret = 0;
  71. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  72. switch (cmd) {
  73. case MYCHAR_IOCTL_GET_MAXLEN:
  74. ret = copy_to_user(pret, &maxlen, sizeof(int));
  75. if (ret) {
  76. printk("copy_to_user MAXLEN failed\n");
  77. return -1;
  78. }
  79. break;
  80. case MYCHAR_IOCTL_GET_CURLEN:
  81. ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
  82. if (ret) {
  83. printk("copy_to_user CURLEN failed\n");
  84. return -1;
  85. }
  86. break;
  87. default:
  88. printk("The cmd is unknow\n");
  89. return -1;
  90. }
  91. return 0;
  92. }
  93. struct file_operations myops = {
  94. .owner = THIS_MODULE,
  95. .open = mychar_open,
  96. .release = mychar_close,
  97. .read = mychar_read,
  98. .write = mychar_write,
  99. .unlocked_ioctl = mychar_ioctl,
  100. };
  101. int __init mychar_init(void)
  102. {
  103. int ret = 0;
  104. dev_t devno = MKDEV(major, minor);
  105. /* 申请设备号 */
  106. ret = register_chrdev_region(devno, mychar_num, "mychar");
  107. if (ret) {
  108. ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
  109. if (ret) {
  110. printk("get devno failed\n");
  111. return -1;
  112. }
  113. major = MAJOR(devno);
  114. }
  115. /* 给struct cdev对象指定操作函数集 */
  116. cdev_init(&gmydev.mydev, &myops);
  117. /* 给struct cdev对象添加到内核对应的数据结构里 */
  118. gmydev.mydev.owner = THIS_MODULE;
  119. cdev_add(&gmydev.mydev, devno, mychar_num);
  120. return 0;
  121. }
  122. void __exit mychar_exit(void)
  123. {
  124. dev_t devno = MKDEV(major, minor);
  125. cdev_del(&gmydev.mydev);
  126. unregister_chrdev_region(devno, mychar_num);
  127. }
  128. MODULE_LICENSE("GPL");
  129. module_init(mychar_init);
  130. module_exit(mychar_exit);

mychar.h

  1. #ifndef MY_CHAR_H
  2. #define MY_CHAR_H
  3. #include <asm/ioctl.h>
  4. #define MY_CHAR_MAGIC 'k'
  5. #define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
  6. #define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)
  7. #endif

test.c

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. #include <sys/ioctl.h>
  7. #include "mychar.h"
  8. int main(int argc, char* argv[])
  9. {
  10. int fd = -1;
  11. char buf[8] = "";
  12. int max = 0;
  13. int cur = 0;
  14. if (argc < 2) {
  15. printf("The argument is too few\n");
  16. return 1;
  17. }
  18. fd = open(argv[1], O_RDWR);
  19. if (fd < 0) {
  20. printf("open %s failed\n", argv[1]);
  21. return 2;
  22. }
  23. ioctl(fd, MYCHAR_IOCTL_GET_MAXLEN, &max);
  24. printf("max len is %d\n", max);
  25. write(fd, "hello", 6);
  26. ioctl(fd, MYCHAR_IOCTL_GET_CURLEN, &cur);
  27. printf("cur len is %d\n",cur);
  28. read(fd, buf, 8);
  29. printf("buf = %s\n",buf);
  30. close(fd);
  31. fd = -1;
  32. return 0;
  33. }

测试结果

  1. shrek@ubuntu16:~/share/mydrivercode$ sudo mknod /dev/mydev c 11 0
  2. shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./mychar.ko
  3. shrek@ubuntu16:~/share/mydrivercode$ ./a.out /dev/mydev
  4. max len is 100
  5. cur len is 6
  6. buf = hello

printk

  1. //日志级别
  2. #define    KERN_EMERG    "<0>"    /* system is unusable            */
  3. #define    KERN_ALERT    "<1>"    /* action must be taken immediately    */
  4. #define    KERN_CRIT    "<2>"    /* critical conditions            */
  5. #define    KERN_ERR    "<3>"    /* error conditions            */
  6. #define    KERN_WARNING    "<4>"    /* warning conditions            */
  7. #define    KERN_NOTICE    "<5>"    /* normal but significant condition    */
  8. #define    KERN_INFO    "<6>"    /* informational            */
  9. #define    KERN_DEBUG    "<7>"    /* debug-level messages            */

用法:printk(KERN_INFO"....",....)
printk(KERN_INFO"Hello World"); =====> printk("<6>""Hello World") ====> printk("<6>Hello World")

指定 dmesg 打印的内容:dmesg --level=emerg,alert,crit,err,warn,notice,info,debug

自定义标签:

  1. #define HELLO_DEBUG
  2. #undef PDEBUG
  3. #ifdef HELLO_DEBUG
  4. #define PDEBUG(fmt, args...) printk(KERN_DEBUG fmt, ##args)
  5. #else
  6. #define PDEBUG(fmt, args...)
  7. #endif

阻塞和非阻塞

应用层:

方法一:open时由 O_NONBLOCK 指示read、write时是否阻塞

方法二:open以后可以由fcntl函数来改变是否阻塞:

  1. flags = fcntl(fd, F_GETFL, 0);
  2. flags |= O_NONBLOCK;
  3. fcntl(fd, F_SETFL, flags);

驱动层:

通过等待队列

  1. wait_queue_head_t //等待队列头数据类型
  2. init_waitqueue_head(wait_queue_head_t *pwq) //初始化等待队列头
  3.     
  4. wait_event_interruptible(wq,condition)
  5. /*
  6. 功能:条件不成立则让任务进入浅度睡眠,直到条件成立醒来
  7.     wq:等待队列头
  8.     condition:C语言表达式
  9. 返回:正常唤醒返回0,信号唤醒返回非0(此时读写操作函数应返回-ERESTARTSYS)
  10. */
  11.         
  12. wait_event(wq,condition) //深度睡眠
  13. wake_up_interruptible(wait_queue_head_t *pwq)
  14.         
  15. wake_up(wait_queue_head_t *pwq)
  16.     
  17.     
  18. /*
  19. 1. 读、写用不同的等待队列头rq、wq
  20. 2. 无数据可读、可写时调用wait_event_interruptible(rq、wq,条件)
  21. 3. 写入数据成功时唤醒rq,读出数据成功唤醒wq
  22. */

示例代码(阻塞和非阻塞)

mychar.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/wait.h>
  6. #include <linux/sched.h>
  7. #include <linux/uaccess.h>
  8. #include "mychar.h"
  9. #define BUF_LEN 100
  10. int major = 11;
  11. int minor = 0;
  12. int mychar_num = 1;
  13. struct mychar_dev
  14. {
  15. struct cdev mydev;
  16. char mydev_buf[BUF_LEN];
  17. int curlen;
  18. wait_queue_head_t rq;
  19. wait_queue_head_t wq;
  20. };
  21. struct mychar_dev gmydev;
  22. int mychar_open(struct inode* pnode, struct file* pfile)
  23. {
  24. pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
  25. printk("mychar_open is called\n");
  26. return 0;
  27. }
  28. int mychar_close(struct inode* pnode, struct file* pfile)
  29. {
  30. printk("mychar_close is called\n");
  31. return 0;
  32. }
  33. ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
  34. {
  35. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  36. int size = 0;
  37. int ret = 0;
  38. if (pmydev->curlen <= 0) {
  39. if (pfile->f_flags & O_NONBLOCK) {
  40. // 非阻塞
  41. printk("O_NONBLOCK No Data Read\n");
  42. return -1;
  43. } else {
  44. // 阻塞
  45. ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
  46. if (ret) {
  47. printk("Wake up by signal\n");
  48. return -ERESTARTSYS;
  49. }
  50. }
  51. }
  52. if (count > pmydev->curlen) {
  53. size = pmydev->curlen;
  54. } else {
  55. size = count;
  56. }
  57. ret = copy_to_user(puser, pmydev->mydev_buf, size);
  58. if (ret) {
  59. printk("copy_to_user failed\n");
  60. return -1;
  61. }
  62. memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);
  63. pmydev->curlen -= size;
  64. wake_up_interruptible(&pmydev->wq);
  65. return size;
  66. }
  67. ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
  68. {
  69. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  70. int size = 0;
  71. int ret = 0;
  72. if (pmydev->curlen >= BUF_LEN) {
  73. if (pfile->f_flags & O_NONBLOCK) {
  74. printk("O_NONBLOCK Can not write data\n");
  75. return -1;
  76. } else {
  77. ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
  78. if (ret) {
  79. printk("Wake up by signal\n");
  80. return -ERESTARTSYS;
  81. }
  82. }
  83. }
  84. if (count > BUF_LEN - pmydev->curlen) {
  85. size = BUF_LEN - pmydev->curlen;
  86. } else {
  87. size = count;
  88. }
  89. ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
  90. if (ret) {
  91. printk("copy_from_user failed\n");
  92. return -1;
  93. }
  94. pmydev->curlen += size;
  95. wake_up_interruptible(&pmydev->rq);
  96. return size;
  97. }
  98. long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
  99. {
  100. int __user *pret = (int*)arg;
  101. int maxlen = BUF_LEN;
  102. int ret = 0;
  103. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  104. switch (cmd) {
  105. case MYCHAR_IOCTL_GET_MAXLEN:
  106. ret = copy_to_user(pret, &maxlen, sizeof(int));
  107. if (ret) {
  108. printk("copy_to_user MAXLEN failed\n");
  109. return -1;
  110. }
  111. break;
  112. case MYCHAR_IOCTL_GET_CURLEN:
  113. ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
  114. if (ret) {
  115. printk("copy_to_user CURLEN failed\n");
  116. return -1;
  117. }
  118. break;
  119. default:
  120. printk("The cmd is unknow\n");
  121. return -1;
  122. }
  123. return 0;
  124. }
  125. struct file_operations myops = {
  126. .owner = THIS_MODULE,
  127. .open = mychar_open,
  128. .release = mychar_close,
  129. .read = mychar_read,
  130. .write = mychar_write,
  131. .unlocked_ioctl = mychar_ioctl,
  132. };
  133. int __init mychar_init(void)
  134. {
  135. int ret = 0;
  136. dev_t devno = MKDEV(major, minor);
  137. /* 申请设备号 */
  138. ret = register_chrdev_region(devno, mychar_num, "mychar");
  139. if (ret) {
  140. ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
  141. if (ret) {
  142. printk("get devno failed\n");
  143. return -1;
  144. }
  145. major = MAJOR(devno);
  146. }
  147. /* 给struct cdev对象指定操作函数集 */
  148. cdev_init(&gmydev.mydev, &myops);
  149. /* 给struct cdev对象添加到内核对应的数据结构里 */
  150. gmydev.mydev.owner = THIS_MODULE;
  151. cdev_add(&gmydev.mydev, devno, mychar_num);
  152. init_waitqueue_head(&gmydev.rq);
  153. init_waitqueue_head(&gmydev.wq);
  154. return 0;
  155. }
  156. void __exit mychar_exit(void)
  157. {
  158. dev_t devno = MKDEV(major, minor);
  159. cdev_del(&gmydev.mydev);
  160. unregister_chrdev_region(devno, mychar_num);
  161. }
  162. MODULE_LICENSE("GPL");
  163. module_init(mychar_init);
  164. module_exit(mychar_exit);

mychar.h同ioctl

test.c(测试非阻塞,如果测试阻塞则将 O_NONBLOCK 去掉即可)

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. #include <sys/ioctl.h>
  7. #include "mychar.h"
  8. int main(int argc, char* argv[])
  9. {
  10. int fd = -1;
  11. char buf[8] = "";
  12. int ret = 0;
  13. if (argc < 2) {
  14. printf("The argument is too few\n");
  15. return 1;
  16. }
  17. fd = open(argv[1], O_RDWR | O_NONBLOCK);
  18. if (fd < 0) {
  19. printf("open %s failed\n", argv[1]);
  20. return 2;
  21. }
  22. ret = read(fd, buf, 8);
  23. if (ret < 0) {
  24. printf("read data failed\n");
  25. } else {
  26. printf("buf = %s\n",buf);
  27. }
  28. close(fd);
  29. fd = -1;
  30. return 0;
  31. }

测试结果

非阻塞:

  1. shrek@ubuntu16:~/share/mydrivercode$ sudo mknod /dev/mydev c 11 0
  2. shrek@ubuntu16:~/share/mydrivercode$ sudo chmod a+w /dev/mydev
  3. shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./mychar.ko
  4. shrek@ubuntu16:~/share/mydrivercode$ ./a.out /dev/mydev
  5. read data failed

阻塞:

执行测试代码一直阻塞,直至给设备写入数据结束阻塞

  1. shrek@ubuntu16:~/share/mydrivercode$ echo "hello" > /dev/mydev
  2. shrek@ubuntu16:~/share/mydrivercode$ ./a.out /dev/mydev
  3. buf = hello

多路复用

应用层

select:位运算实现,监控的描述符数量有限(32位机1024,64位机2048),效率差
poll:链表实现,监控的描述符数量不限,效率差
epoll:效率最高,监控的描述符数量不限

select:

  1. int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
  2. /* 功能:监听多个描述符,阻塞等待有一个或者多个文件描述符,准备就绪。
  3. 内核将没有准备就绪的文件描述符,从集合中清掉了。
  4. 参数: nfds 最大文件描述符数 ,加1
  5. readfds 读文件描述符集合
  6. writefds 写文件描述符集合
  7. exceptfds 其他异常的文件描述符集合
  8. timeout 超时时间(NULL)
  9. 返回值:当timeout为NULL时返回0,成功:准备好的文件描述的个数 出错:-1
  10. 当timeout不为NULL时,如超时设置为0,则select为非阻塞,超时设置 > 0,则无描述符可被操作的情况下阻塞指定长度的时间
  11. */
  12. void FD_CLR(int fd, fd_set *set);
  13. //功能:将fd 从集合中清除掉
  14. int FD_ISSET(int fd, fd_set *set);
  15. //功能:判断fd 是否存在于集合中
  16. void FD_SET(int fd, fd_set *set);
  17. //功能:将fd 添加到集合中
  18. void FD_ZERO(fd_set *set);
  19. //功能:将集合清零
  20. //使用模型:
  21. while(1)
  22. {
  23. /*得到最大的描述符maxfd*/
  24. /*FD_ZERO清空描述符集合*/
  25. /*将被监控描述符加到相应集合rfds里 FD_SET*/
  26. /*设置超时*/
  27. ret = select(maxfd+1,&rfds,&wfds,NULL,NULL);
  28. if(ret < 0)
  29. {
  30. if(errno == EINTR)//错误时信号引起的
  31. {
  32. continue;
  33. }
  34. else
  35. {
  36. break;
  37. }
  38. }
  39. else if(ret == 0)
  40. {//超时
  41. //.....
  42. }
  43. else
  44. { //> 0 ret为可被操作的描述符个数
  45. if(FD_ISSET(fd1,&rfds))
  46. {//读数据
  47. //....
  48. }
  49. if(FD_ISSET(fd2,&rfds))
  50. {//读数据
  51. //....
  52. }
  53. ///.....
  54. if(FD_ISSET(fd1,&wfds))
  55. {//写数据
  56. //....
  57. }
  58. }
  59. }

驱动层

  1. void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
  2. /*功能:将等待队列头添加至poll_table表中
  3. 参数:struct file :设备文件
  4. Wait_queue_head_t :等待队列头
  5. Poll_table :poll_table表
  6. */
  7. /*该函数与select、poll、epoll_wait函数相对应,协助这些多路监控函数判断本设备是否有数据可读写*/
  8. unsigned int xxx_poll(struct file *filp, poll_table *wait) //函数名初始化给struct file_operations的成员.poll
  9. {
  10. unsigned int mask = 0;
  11. /*
  12. 1. 将所有等待队列头加入poll_table表中
  13. 2. 判断是否可读,如可读则mask |= POLLIN | POLLRDNORM;
  14. 3. 判断是否可写,如可写则mask |= POLLOUT | POLLWRNORM;
  15. */
  16. return mask;
  17. }

示例代码(select)

mychar.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/wait.h>
  6. #include <linux/sched.h>
  7. #include <linux/poll.h>
  8. #include <linux/uaccess.h>
  9. #include "mychar.h"
  10. #define BUF_LEN 100
  11. int major = 11;
  12. int minor = 0;
  13. int mychar_num = 1;
  14. struct mychar_dev
  15. {
  16. struct cdev mydev;
  17. char mydev_buf[BUF_LEN];
  18. int curlen;
  19. wait_queue_head_t rq;
  20. wait_queue_head_t wq;
  21. };
  22. struct mychar_dev gmydev;
  23. int mychar_open(struct inode* pnode, struct file* pfile)
  24. {
  25. pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
  26. printk("mychar_open is called\n");
  27. return 0;
  28. }
  29. int mychar_close(struct inode* pnode, struct file* pfile)
  30. {
  31. printk("mychar_close is called\n");
  32. return 0;
  33. }
  34. ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
  35. {
  36. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  37. int size = 0;
  38. int ret = 0;
  39. if (pmydev->curlen <= 0) {
  40. if (pfile->f_flags & O_NONBLOCK) {
  41. // 非阻塞
  42. printk("O_NONBLOCK No Data Read\n");
  43. return -1;
  44. } else {
  45. // 阻塞
  46. ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
  47. if (ret) {
  48. printk("Wake up by signal\n");
  49. return -ERESTARTSYS;
  50. }
  51. }
  52. }
  53. if (count > pmydev->curlen) {
  54. size = pmydev->curlen;
  55. } else {
  56. size = count;
  57. }
  58. ret = copy_to_user(puser, pmydev->mydev_buf, size);
  59. if (ret) {
  60. printk("copy_to_user failed\n");
  61. return -1;
  62. }
  63. memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);
  64. pmydev->curlen -= size;
  65. wake_up_interruptible(&pmydev->wq);
  66. return size;
  67. }
  68. ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
  69. {
  70. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  71. int size = 0;
  72. int ret = 0;
  73. if (pmydev->curlen >= BUF_LEN) {
  74. if (pfile->f_flags & O_NONBLOCK) {
  75. printk("O_NONBLOCK Can not write data\n");
  76. return -1;
  77. } else {
  78. ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
  79. if (ret) {
  80. printk("Wake up by signal\n");
  81. return -ERESTARTSYS;
  82. }
  83. }
  84. }
  85. if (count > BUF_LEN - pmydev->curlen) {
  86. size = BUF_LEN - pmydev->curlen;
  87. } else {
  88. size = count;
  89. }
  90. ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
  91. if (ret) {
  92. printk("copy_from_user failed\n");
  93. return -1;
  94. }
  95. pmydev->curlen += size;
  96. wake_up_interruptible(&pmydev->rq);
  97. return size;
  98. }
  99. long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
  100. {
  101. int __user *pret = (int*)arg;
  102. int maxlen = BUF_LEN;
  103. int ret = 0;
  104. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  105. switch (cmd) {
  106. case MYCHAR_IOCTL_GET_MAXLEN:
  107. ret = copy_to_user(pret, &maxlen, sizeof(int));
  108. if (ret) {
  109. printk("copy_to_user MAXLEN failed\n");
  110. return -1;
  111. }
  112. break;
  113. case MYCHAR_IOCTL_GET_CURLEN:
  114. ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
  115. if (ret) {
  116. printk("copy_to_user CURLEN failed\n");
  117. return -1;
  118. }
  119. break;
  120. default:
  121. printk("The cmd is unknow\n");
  122. return -1;
  123. }
  124. return 0;
  125. }
  126. unsigned int mychar_poll(struct file* pfile, poll_table* ptb)
  127. {
  128. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  129. unsigned int mask = 0;
  130. poll_wait(pfile, &pmydev->rq, ptb);
  131. poll_wait(pfile, &pmydev->wq, ptb);
  132. if (pmydev->curlen > 0) {
  133. mask |= POLLIN | POLLRDNORM;
  134. }
  135. if (pmydev->curlen < BUF_LEN) {
  136. mask |= POLLOUT | POLLWRNORM;
  137. }
  138. return mask;
  139. }
  140. struct file_operations myops = {
  141. .owner = THIS_MODULE,
  142. .open = mychar_open,
  143. .release = mychar_close,
  144. .read = mychar_read,
  145. .write = mychar_write,
  146. .unlocked_ioctl = mychar_ioctl,
  147. .poll = mychar_poll,
  148. };
  149. int __init mychar_init(void)
  150. {
  151. int ret = 0;
  152. dev_t devno = MKDEV(major, minor);
  153. /* 申请设备号 */
  154. ret = register_chrdev_region(devno, mychar_num, "mychar");
  155. if (ret) {
  156. ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
  157. if (ret) {
  158. printk("get devno failed\n");
  159. return -1;
  160. }
  161. major = MAJOR(devno);
  162. }
  163. /* 给struct cdev对象指定操作函数集 */
  164. cdev_init(&gmydev.mydev, &myops);
  165. /* 给struct cdev对象添加到内核对应的数据结构里 */
  166. gmydev.mydev.owner = THIS_MODULE;
  167. cdev_add(&gmydev.mydev, devno, mychar_num);
  168. init_waitqueue_head(&gmydev.rq);
  169. init_waitqueue_head(&gmydev.wq);
  170. return 0;
  171. }
  172. void __exit mychar_exit(void)
  173. {
  174. dev_t devno = MKDEV(major, minor);
  175. cdev_del(&gmydev.mydev);
  176. unregister_chrdev_region(devno, mychar_num);
  177. }
  178. MODULE_LICENSE("GPL");
  179. module_init(mychar_init);
  180. module_exit(mychar_exit);

mychar.h同ioctl

test.c

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. #include <sys/ioctl.h>
  7. #include <sys/select.h>
  8. #include <sys/time.h>
  9. #include <errno.h>
  10. #include "mychar.h"
  11. int main(int argc, char* argv[])
  12. {
  13. int fd = -1;
  14. char buf[8] = "";
  15. int ret = 0;
  16. fd_set rfds;
  17. if (argc < 2) {
  18. printf("The argument is too few\n");
  19. return 1;
  20. }
  21. fd = open(argv[1], O_RDWR);
  22. if (fd < 0) {
  23. printf("open %s failed\n", argv[1]);
  24. return 2;
  25. }
  26. while (1) {
  27. FD_ZERO(&rfds);
  28. FD_SET(fd, &rfds);
  29. ret = select(fd + 1, &rfds, NULL, NULL, NULL);
  30. if (ret < 0) {
  31. if (errno == EINTR) {
  32. continue;
  33. } else {
  34. printf("select error\n");
  35. break;
  36. }
  37. }
  38. if (FD_ISSET(fd, &rfds)) {
  39. read(fd, buf, 8);
  40. printf("buf = %s\n", buf);
  41. }
  42. }
  43. close(fd);
  44. fd = -1;
  45. return 0;
  46. }

测试结果

执行测试代码后一直等待是否有数据可读,有则读取数据

  1. shrek@ubuntu16:~/share/mydrivercode$ echo "hello" > /dev/mydev
  2. shrek@ubuntu16:~/share/mydrivercode$ echo "hello" > /dev/mydev
  3. shrek@ubuntu16:~/share/mydrivercode$ echo "hello" > /dev/mydev
  4. shrek@ubuntu16:~/share/mydrivercode$ echo "hello" > /dev/mydev
  1. shrek@ubuntu16:~/share/mydrivercode$ ./a.out /dev/mydev
  2. buf = hello
  3. buf = hello
  4. buf = hello
  5. buf = hello

信号驱动

Tips

信号驱动存在丢失信号的风险

应用层:信号注册+fcntl

  1. signal(SIGIO, input_handler); //注册信号处理函数
  2. fcntl(fd, F_SETOWN, getpid());//将描述符设置给对应进程,好由描述符获知PID
  3. oflags = fcntl(fd, F_GETFL);
  4. fcntl(fd, F_SETFL, oflags | FASYNC);//将该设备的IO模式设置成信号驱动模式
  5. void input_handler(int signum)//应用自己实现的信号处理函数,在此函数中完成读写
  6. {
  7. //读数据
  8. }
  9. //应用模板
  10. int main()
  11. {
  12. int fd = open("/dev/xxxx",O_RDONLY);
  13. fcntl(fd, F_SETOWN, getpid());
  14. oflags = fcntl(fd, F_GETFL);
  15. fcntl(fd, F_SETFL, oflags | FASYNC);
  16. signal(SIGIO,xxxx_handler);
  17. //......
  18. }
  19. void xxxx_handle(int signo)
  20. {//读写数据
  21. }

驱动层:实现fasync函数

  1. /*设备结构中添加如下成员*/
  2. struct fasync_struct *pasync_obj;
  3. /*应用调用fcntl设置FASYNC时调用该函数产生异步通知结构对象,并将其地址设置到设备结构成员中*/
  4. static int hello_fasync(int fd, struct file *filp, int mode) //函数名初始化给struct file_operations的成员.fasync
  5. {
  6. struct hello_device *dev = filp->private_data;
  7. return fasync_helper(fd, filp, mode, &dev->pasync_obj);
  8. }
  9. /*写函数中有数据可读时向应用层发信号*/
  10. if (dev->pasync_obj)
  11. kill_fasync(&dev->pasync_obj, SIGIO, POLL_IN);
  12. /*release函数中释放异步通知结构对象*/
  13. if (dev->pasync_obj)
  14. fasync_helper(-1, filp, 0, &dev->pasync_obj);
  15. int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **pp);
  16. /*
  17. 功能:产生或释放异步通知结构对象
  18. 参数:
  19. 返回值:成功为>=0,失败负数
  20. */
  21. void kill_fasync(struct fasync_struct **, int, int);
  22. /*
  23. 功能:发信号
  24. 参数:
  25. struct fasync_struct ** 指向保存异步通知结构地址的指针
  26. int 信号 SIGIO/SIGKILL/SIGCHLD/SIGCONT/SIGSTOP
  27. int 读写信息POLLIN、POLLOUT
  28. */

示例代码(信号驱动)

mychar.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/wait.h>
  6. #include <linux/sched.h>
  7. #include <linux/poll.h>
  8. #include <linux/uaccess.h>
  9. #include "mychar.h"
  10. #define BUF_LEN 100
  11. int major = 11;
  12. int minor = 0;
  13. int mychar_num = 1;
  14. struct mychar_dev
  15. {
  16. struct cdev mydev;
  17. char mydev_buf[BUF_LEN];
  18. int curlen;
  19. wait_queue_head_t rq;
  20. wait_queue_head_t wq;
  21. struct fasync_struct* pasync_obj;
  22. };
  23. struct mychar_dev gmydev;
  24. int mychar_open(struct inode* pnode, struct file* pfile)
  25. {
  26. pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
  27. return 0;
  28. }
  29. int mychar_close(struct inode* pnode, struct file* pfile)
  30. {
  31. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  32. if (pmydev->pasync_obj != NULL) {
  33. fasync_helper(-1, pfile, 0, &pmydev->pasync_obj);
  34. }
  35. return 0;
  36. }
  37. ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
  38. {
  39. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  40. int size = 0;
  41. int ret = 0;
  42. if (pmydev->curlen <= 0) {
  43. if (pfile->f_flags & O_NONBLOCK) {
  44. // 非阻塞
  45. printk("O_NONBLOCK No Data Read\n");
  46. return -1;
  47. } else {
  48. // 阻塞
  49. ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
  50. if (ret) {
  51. printk("Wake up by signal\n");
  52. return -ERESTARTSYS;
  53. }
  54. }
  55. }
  56. if (count > pmydev->curlen) {
  57. size = pmydev->curlen;
  58. } else {
  59. size = count;
  60. }
  61. ret = copy_to_user(puser, pmydev->mydev_buf, size);
  62. if (ret) {
  63. printk("copy_to_user failed\n");
  64. return -1;
  65. }
  66. memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);
  67. pmydev->curlen -= size;
  68. wake_up_interruptible(&pmydev->wq);
  69. return size;
  70. }
  71. ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
  72. {
  73. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  74. int size = 0;
  75. int ret = 0;
  76. if (pmydev->curlen >= BUF_LEN) {
  77. if (pfile->f_flags & O_NONBLOCK) {
  78. printk("O_NONBLOCK Can not write data\n");
  79. return -1;
  80. } else {
  81. ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
  82. if (ret) {
  83. printk("Wake up by signal\n");
  84. return -ERESTARTSYS;
  85. }
  86. }
  87. }
  88. if (count > BUF_LEN - pmydev->curlen) {
  89. size = BUF_LEN - pmydev->curlen;
  90. } else {
  91. size = count;
  92. }
  93. ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
  94. if (ret) {
  95. printk("copy_from_user failed\n");
  96. return -1;
  97. }
  98. pmydev->curlen += size;
  99. wake_up_interruptible(&pmydev->rq);
  100. if (pmydev->pasync_obj != NULL) {
  101. kill_fasync(&pmydev->pasync_obj, SIGIO, POLL_IN);
  102. }
  103. return size;
  104. }
  105. long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
  106. {
  107. int __user *pret = (int*)arg;
  108. int maxlen = BUF_LEN;
  109. int ret = 0;
  110. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  111. switch (cmd) {
  112. case MYCHAR_IOCTL_GET_MAXLEN:
  113. ret = copy_to_user(pret, &maxlen, sizeof(int));
  114. if (ret) {
  115. printk("copy_to_user MAXLEN failed\n");
  116. return -1;
  117. }
  118. break;
  119. case MYCHAR_IOCTL_GET_CURLEN:
  120. ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
  121. if (ret) {
  122. printk("copy_to_user CURLEN failed\n");
  123. return -1;
  124. }
  125. break;
  126. default:
  127. printk("The cmd is unknow\n");
  128. return -1;
  129. }
  130. return 0;
  131. }
  132. unsigned int mychar_poll(struct file* pfile, poll_table* ptb)
  133. {
  134. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  135. unsigned int mask = 0;
  136. poll_wait(pfile, &pmydev->rq, ptb);
  137. poll_wait(pfile, &pmydev->wq, ptb);
  138. if (pmydev->curlen > 0) {
  139. mask |= POLLIN | POLLRDNORM;
  140. }
  141. if (pmydev->curlen < BUF_LEN) {
  142. mask |= POLLOUT | POLLWRNORM;
  143. }
  144. return mask;
  145. }
  146. int mychar_fasync(int fd, struct file* pfile, int mode) {
  147. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  148. return fasync_helper(fd, pfile, mode, &pmydev->pasync_obj);
  149. }
  150. struct file_operations myops = {
  151. .owner = THIS_MODULE,
  152. .open = mychar_open,
  153. .release = mychar_close,
  154. .read = mychar_read,
  155. .write = mychar_write,
  156. .unlocked_ioctl = mychar_ioctl,
  157. .poll = mychar_poll,
  158. .fasync = mychar_fasync,
  159. };
  160. int __init mychar_init(void)
  161. {
  162. int ret = 0;
  163. dev_t devno = MKDEV(major, minor);
  164. /* 申请设备号 */
  165. ret = register_chrdev_region(devno, mychar_num, "mychar");
  166. if (ret) {
  167. ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
  168. if (ret) {
  169. printk("get devno failed\n");
  170. return -1;
  171. }
  172. major = MAJOR(devno);
  173. }
  174. /* 给struct cdev对象指定操作函数集 */
  175. cdev_init(&gmydev.mydev, &myops);
  176. /* 给struct cdev对象添加到内核对应的数据结构里 */
  177. gmydev.mydev.owner = THIS_MODULE;
  178. cdev_add(&gmydev.mydev, devno, mychar_num);
  179. init_waitqueue_head(&gmydev.rq);
  180. init_waitqueue_head(&gmydev.wq);
  181. return 0;
  182. }
  183. void __exit mychar_exit(void)
  184. {
  185. dev_t devno = MKDEV(major, minor);
  186. cdev_del(&gmydev.mydev);
  187. unregister_chrdev_region(devno, mychar_num);
  188. }
  189. MODULE_LICENSE("GPL");
  190. module_init(mychar_init);
  191. module_exit(mychar_exit);

mychar.h同ioctl

test.c

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. #include <sys/ioctl.h>
  7. #include <signal.h>
  8. #include "mychar.h"
  9. int fd = -1;
  10. void sigio_handler(int signo);
  11. int main(int argc, char* argv[])
  12. {
  13. int flg = 0;
  14. if (argc < 2) {
  15. printf("The argument is too few\n");
  16. return 1;
  17. }
  18. signal(SIGIO, sigio_handler);
  19. fd = open(argv[1], O_RDWR);
  20. if (fd < 0) {
  21. printf("open %s failed\n", argv[1]);
  22. return 2;
  23. }
  24. fcntl(fd, F_SETOWN, getpid());
  25. flg = fcntl(fd, F_GETFL);
  26. flg |= FASYNC;
  27. fcntl(fd, F_SETFL, flg);
  28. while (1) {}
  29. close(fd);
  30. fd = -1;
  31. return 0;
  32. }
  33. void sigio_handler(int signo)
  34. {
  35. char buf[8] = "";
  36. read(fd, buf, 8);
  37. printf("buf = %s\n", buf);
  38. }

测试结果同多路复用


并发控制

上下文和并发场合

执行流:有开始有结束总体顺序执行的一段代码  又称上下文

应用编程:任务上下文
内核编程:

1. 任务上下文:五状态 可阻塞
   a. 应用进程或线程运行在用户空间
   b. 应用进程或线程运行在内核空间(通过调用syscall来间接使用内核空间)
   c. 内核线程始终在内核空间
2. 异常上下文:不可阻塞
   中断上下文

竞态:多任务并行执行时,如果在一个时刻同时操作同一个资源,会引起资源的错乱,这种错乱情形被称为竞态

共享资源:可能会被多个任务同时使用的资源

临界区:操作共享资源的代码段

为了解决竞态,需要提供一种控制机制,来避免在同一时刻使用共享资源,这种机制被称为并发控制机制

并发控制机制分类:

1. 原子操作类
2. 忙等待类
3. 阻塞类

通用并发控制机制的一般使用套路:

  1. /*互斥问题:*/
  2. 并发控制机制初始化为可用
  3. P操作
  4. 临界区
  5. V操作
  6. /*同步问题:*/
  7. // 并发控制机制初始化为不可用
  8. // 先行方:
  9. ......
  10. V操作
  11. // 后行方:
  12. P操作
  13. ......

中断屏蔽

一种同步机制的辅助手段

禁止本cpu中断                   使能本cpu中断
local_irq_disable();            local_irq_enable();            
local_irq_save(flags);        local_irq_restore(flags);    与cpu的中断位相关
local_bh_disable();            local_bh_enable();            与中断低半部有关,关闭、打开软中断

禁止中断
临界区    //临界区代码不能占用太长时间,需要很快完成
打开中断

适用场合:中断上下文与某任务共享资源时,或多个不同优先级的中断上下文间共享资源时

原子变量

原子变量:存取不可被打断的特殊整型变量
a.设置原子量的值
void atomic_set(atomic_t *v,int i);    // 设置原子量的值为i
atomic_t v = ATOMIC_INIT(0);        // 定义原子变量v并初始化为0

v = 10;//错误

b.获取原子量的值
atomic_read(atomic_t *v);               // 返回原子量的值

c.原子变量加减
void atomic_add(int i,atomic_t *v);  // 原子变量增加i
void atomic_sub(int i,atomic_t *v);  // 原子变量减少i

d.原子变量自增自减
void atomic_inc(atomic_t *v);         // 原子变量增加1
void atomic_dec(atomic_t *v);        // 原子变量减少1

e.操作并测试:运算后结果为0则返回真,否则返回假
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i,atomic_t *v);

原子位操作方法:
a.设置位
void set_bit(nr, void *addr);            // 设置addr的第nr位为1
b.清除位
void clear_bit(nr , void *addr);        // 清除addr的第nr位为0
c.改变位
void change_bit(nr , void *addr);    // 改变addr的第nr位为1
d.测试位
void test_bit(nr , void *addr);           // 测试addr的第nr位是否为1

适用场合:共享资源为单个整型变量的互斥场合


示例代码(原子变量,使设备文件同一时间只能打开一次)

openonce.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/wait.h>
  6. #include <linux/sched.h>
  7. #include <linux/poll.h>
  8. #include <linux/uaccess.h>
  9. #include <asm/atomic.h>
  10. int major = 11;
  11. int minor = 0;
  12. int openonce_num = 1;
  13. struct openonce_dev
  14. {
  15. struct cdev mydev;
  16. atomic_t openflag;
  17. };
  18. struct openonce_dev gmydev;
  19. int openonce_open(struct inode* pnode, struct file* pfile)
  20. {
  21. struct openonce_dev* pmydev = NULL;
  22. pfile->private_data = (void *)(container_of(pnode->i_cdev, struct openonce_dev, mydev));
  23. pmydev = (struct openonce_dev*)pfile->private_data;
  24. if (atomic_dec_and_test(&pmydev->openflag)) {
  25. return 0;
  26. } else {
  27. atomic_inc(&pmydev->openflag);
  28. printk("The device is opened already\n");
  29. return -1;
  30. }
  31. return 0;
  32. }
  33. int openonce_close(struct inode* pnode, struct file* pfile)
  34. {
  35. struct openonce_dev *pmydev = (struct openonce_dev *)pfile->private_data;
  36. atomic_set(&pmydev->openflag, 1);
  37. return 0;
  38. }
  39. struct file_operations myops = {
  40. .owner = THIS_MODULE,
  41. .open = openonce_open,
  42. .release = openonce_close,
  43. };
  44. int __init openonce_init(void)
  45. {
  46. int ret = 0;
  47. dev_t devno = MKDEV(major, minor);
  48. /* 申请设备号 */
  49. ret = register_chrdev_region(devno, openonce_num, "openonce");
  50. if (ret) {
  51. ret = alloc_chrdev_region(&devno, minor, openonce_num, "openonce");
  52. if (ret) {
  53. printk("get devno failed\n");
  54. return -1;
  55. }
  56. major = MAJOR(devno);
  57. }
  58. /* 给struct cdev对象指定操作函数集 */
  59. cdev_init(&gmydev.mydev, &myops);
  60. /* 给struct cdev对象添加到内核对应的数据结构里 */
  61. gmydev.mydev.owner = THIS_MODULE;
  62. cdev_add(&gmydev.mydev, devno, openonce_num);
  63. atomic_set(&gmydev.openflag, 1);
  64. return 0;
  65. }
  66. void __exit openonce_exit(void)
  67. {
  68. dev_t devno = MKDEV(major, minor);
  69. cdev_del(&gmydev.mydev);
  70. unregister_chrdev_region(devno, openonce_num);
  71. }
  72. MODULE_LICENSE("GPL");
  73. module_init(openonce_init);
  74. module_exit(openonce_exit);

test.c

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. #include <sys/ioctl.h>
  7. int main(int argc, char* argv[])
  8. {
  9. int fd = -1;
  10. if (argc < 2) {
  11. printf("The argument is too few\n");
  12. return 1;
  13. }
  14. fd = open(argv[1], O_RDONLY);
  15. if (fd < 0) {
  16. printf("open %s failed\n", argv[1]);
  17. return 2;
  18. }
  19. while (1) {}
  20. close(fd);
  21. fd = -1;
  22. return 0;
  23. }

测试结果

第一次打开设备文件成功,第二次打开设备文件失败


自旋锁:基于忙等待的并发控制机制

a.定义自旋锁
spinlock_t  lock;

b.初始化自旋锁
spin_lock_init(spinlock_t *);

c.获得自旋锁
spin_lock(spinlock_t *);    //成功获得自旋锁立即返回,否则自旋在那里直到该自旋锁的保持者释放

spin_trylock(spinlock_t *);    //成功获得自旋锁立即返回真,否则返回假,而不是像上一个那样"在原地打转”

d.释放自旋锁
spin_unlock(spinlock_t *);

  1. #include <linux/spinlock.h>
  2. 定义spinlock_t类型的变量lock
  3. spin_lock_init(&lock)后才能正常使用spinlock
  4. spin_lock(&lock);
  5. 临界区
  6. spin_unlock(&lock);

适用场合:

1. 异常上下文之间或异常上下文与任务上下文之间共享资源时
2. 任务上下文之间且临界区执行时间很短时
3. 互斥问题


示例代码(自旋锁)

openonce.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/wait.h>
  6. #include <linux/sched.h>
  7. #include <linux/poll.h>
  8. #include <linux/uaccess.h>
  9. #include <asm/atomic.h>
  10. int major = 11;
  11. int minor = 0;
  12. int openonce_num = 1;
  13. struct openonce_dev
  14. {
  15. struct cdev mydev;
  16. int openflag;
  17. spinlock_t lock;
  18. };
  19. struct openonce_dev gmydev;
  20. int openonce_open(struct inode* pnode, struct file* pfile)
  21. {
  22. struct openonce_dev* pmydev = NULL;
  23. pfile->private_data = (void *)(container_of(pnode->i_cdev, struct openonce_dev, mydev));
  24. pmydev = (struct openonce_dev*)pfile->private_data;
  25. spin_lock(&pmydev->lock);
  26. if (pmydev->openflag) {
  27. pmydev->openflag = 0;
  28. spin_unlock(&pmydev->lock);
  29. return 0;
  30. } else {
  31. spin_unlock(&pmydev->lock);
  32. printk("The device is opened already\n");
  33. return -1;
  34. }
  35. }
  36. int openonce_close(struct inode* pnode, struct file* pfile)
  37. {
  38. struct openonce_dev *pmydev = (struct openonce_dev *)pfile->private_data;
  39. spin_lock(&pmydev->lock);
  40. pmydev->openflag = 1;
  41. spin_unlock(&pmydev->lock);
  42. return 0;
  43. }
  44. struct file_operations myops = {
  45. .owner = THIS_MODULE,
  46. .open = openonce_open,
  47. .release = openonce_close,
  48. };
  49. int __init openonce_init(void)
  50. {
  51. int ret = 0;
  52. dev_t devno = MKDEV(major, minor);
  53. /* 申请设备号 */
  54. ret = register_chrdev_region(devno, openonce_num, "openonce");
  55. if (ret) {
  56. ret = alloc_chrdev_region(&devno, minor, openonce_num, "openonce");
  57. if (ret) {
  58. printk("get devno failed\n");
  59. return -1;
  60. }
  61. major = MAJOR(devno);
  62. }
  63. /* 给struct cdev对象指定操作函数集 */
  64. cdev_init(&gmydev.mydev, &myops);
  65. /* 给struct cdev对象添加到内核对应的数据结构里 */
  66. gmydev.mydev.owner = THIS_MODULE;
  67. cdev_add(&gmydev.mydev, devno, openonce_num);
  68. gmydev.openflag = 1;
  69. spin_lock_init(&gmydev.lock);
  70. return 0;
  71. }
  72. void __exit openonce_exit(void)
  73. {
  74. dev_t devno = MKDEV(major, minor);
  75. cdev_del(&gmydev.mydev);
  76. unregister_chrdev_region(devno, openonce_num);
  77. }
  78. MODULE_LICENSE("GPL");
  79. module_init(openonce_init);
  80. module_exit(openonce_exit);

test.c

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. #include <sys/ioctl.h>
  7. int main(int argc, char* argv[])
  8. {
  9. int fd = -1;
  10. if (argc < 2) {
  11. printf("The argument is too few\n");
  12. return 1;
  13. }
  14. fd = open(argv[1], O_RDONLY);
  15. if (fd < 0) {
  16. printf("open %s failed\n", argv[1]);
  17. return 2;
  18. }
  19. while (1) {}
  20. close(fd);
  21. fd = -1;
  22. return 0;
  23. }

测试结果

第一次打开设备文件成功,第二次打开设备文件失败


信号量:基于阻塞的并发控制机制

a.定义信号量
struct semaphore sem;

b.初始化信号量
void sema_init(struct semaphore *sem, int val);

c.获得信号量P
int down(struct semaphore *sem);//深度睡眠

int down_interruptible(struct semaphore *sem);//浅度睡眠

d.释放信号量V
void up(struct semaphore *sem);


#include <linux/semaphore.h>
 

适用场合:任务上下文之间且临界区执行时间较长时的互斥或同步问题


示例代码(信号量)

mychar.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/wait.h>
  6. #include <linux/sched.h>
  7. #include <linux/poll.h>
  8. #include <linux/uaccess.h>
  9. #include "mychar.h"
  10. #define BUF_LEN 100
  11. int major = 11;
  12. int minor = 0;
  13. int mychar_num = 1;
  14. struct mychar_dev
  15. {
  16. struct cdev mydev;
  17. char mydev_buf[BUF_LEN];
  18. int curlen;
  19. struct semaphore sem;
  20. wait_queue_head_t rq;
  21. wait_queue_head_t wq;
  22. struct fasync_struct* pasync_obj;
  23. };
  24. struct mychar_dev gmydev;
  25. int mychar_open(struct inode* pnode, struct file* pfile)
  26. {
  27. pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
  28. return 0;
  29. }
  30. int mychar_close(struct inode* pnode, struct file* pfile)
  31. {
  32. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  33. if (pmydev->pasync_obj != NULL) {
  34. fasync_helper(-1, pfile, 0, &pmydev->pasync_obj);
  35. }
  36. return 0;
  37. }
  38. ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
  39. {
  40. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  41. int size = 0;
  42. int ret = 0;
  43. down(&pmydev->sem);
  44. if (pmydev->curlen <= 0) {
  45. if (pfile->f_flags & O_NONBLOCK) {
  46. // 非阻塞
  47. up(&pmydev->sem);
  48. printk("O_NONBLOCK No Data Read\n");
  49. return -1;
  50. } else {
  51. // 阻塞
  52. up(&pmydev->sem);
  53. ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
  54. if (ret) {
  55. printk("Wake up by signal\n");
  56. return -ERESTARTSYS;
  57. }
  58. down(&pmydev->sem);
  59. }
  60. }
  61. if (count > pmydev->curlen) {
  62. size = pmydev->curlen;
  63. } else {
  64. size = count;
  65. }
  66. ret = copy_to_user(puser, pmydev->mydev_buf, size);
  67. if (ret) {
  68. up(&pmydev->sem);
  69. printk("copy_to_user failed\n");
  70. return -1;
  71. }
  72. memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);
  73. pmydev->curlen -= size;
  74. up(&pmydev->sem);
  75. wake_up_interruptible(&pmydev->wq);
  76. return size;
  77. }
  78. ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
  79. {
  80. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  81. int size = 0;
  82. int ret = 0;
  83. down(&pmydev->sem);
  84. if (pmydev->curlen >= BUF_LEN) {
  85. if (pfile->f_flags & O_NONBLOCK) {
  86. up(&pmydev->sem);
  87. printk("O_NONBLOCK Can not write data\n");
  88. return -1;
  89. } else {
  90. up(&pmydev->sem);
  91. ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
  92. if (ret) {
  93. printk("Wake up by signal\n");
  94. return -ERESTARTSYS;
  95. }
  96. down(&pmydev->sem);
  97. }
  98. }
  99. if (count > BUF_LEN - pmydev->curlen) {
  100. size = BUF_LEN - pmydev->curlen;
  101. } else {
  102. size = count;
  103. }
  104. ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
  105. if (ret) {
  106. up(&pmydev->sem);
  107. printk("copy_from_user failed\n");
  108. return -1;
  109. }
  110. pmydev->curlen += size;
  111. up(&pmydev->sem);
  112. wake_up_interruptible(&pmydev->rq);
  113. if (pmydev->pasync_obj != NULL) {
  114. kill_fasync(&pmydev->pasync_obj, SIGIO, POLL_IN);
  115. }
  116. return size;
  117. }
  118. long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
  119. {
  120. int __user *pret = (int*)arg;
  121. int maxlen = BUF_LEN;
  122. int ret = 0;
  123. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  124. switch (cmd) {
  125. case MYCHAR_IOCTL_GET_MAXLEN:
  126. ret = copy_to_user(pret, &maxlen, sizeof(int));
  127. if (ret) {
  128. printk("copy_to_user MAXLEN failed\n");
  129. return -1;
  130. }
  131. break;
  132. case MYCHAR_IOCTL_GET_CURLEN:
  133. down(&pmydev->sem);
  134. ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
  135. up(&pmydev->sem);
  136. if (ret) {
  137. printk("copy_to_user CURLEN failed\n");
  138. return -1;
  139. }
  140. break;
  141. default:
  142. printk("The cmd is unknow\n");
  143. return -1;
  144. }
  145. return 0;
  146. }
  147. unsigned int mychar_poll(struct file* pfile, poll_table* ptb)
  148. {
  149. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  150. unsigned int mask = 0;
  151. poll_wait(pfile, &pmydev->rq, ptb);
  152. poll_wait(pfile, &pmydev->wq, ptb);
  153. down(&pmydev->sem);
  154. if (pmydev->curlen > 0) {
  155. mask |= POLLIN | POLLRDNORM;
  156. }
  157. if (pmydev->curlen < BUF_LEN) {
  158. mask |= POLLOUT | POLLWRNORM;
  159. }
  160. up(&pmydev->sem);
  161. return mask;
  162. }
  163. int mychar_fasync(int fd, struct file* pfile, int mode) {
  164. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  165. return fasync_helper(fd, pfile, mode, &pmydev->pasync_obj);
  166. }
  167. struct file_operations myops = {
  168. .owner = THIS_MODULE,
  169. .open = mychar_open,
  170. .release = mychar_close,
  171. .read = mychar_read,
  172. .write = mychar_write,
  173. .unlocked_ioctl = mychar_ioctl,
  174. .poll = mychar_poll,
  175. .fasync = mychar_fasync,
  176. };
  177. int __init mychar_init(void)
  178. {
  179. int ret = 0;
  180. dev_t devno = MKDEV(major, minor);
  181. /* 申请设备号 */
  182. ret = register_chrdev_region(devno, mychar_num, "mychar");
  183. if (ret) {
  184. ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
  185. if (ret) {
  186. printk("get devno failed\n");
  187. return -1;
  188. }
  189. major = MAJOR(devno);
  190. }
  191. /* 给struct cdev对象指定操作函数集 */
  192. cdev_init(&gmydev.mydev, &myops);
  193. /* 给struct cdev对象添加到内核对应的数据结构里 */
  194. gmydev.mydev.owner = THIS_MODULE;
  195. cdev_add(&gmydev.mydev, devno, mychar_num);
  196. init_waitqueue_head(&gmydev.rq);
  197. init_waitqueue_head(&gmydev.wq);
  198. sema_init(&gmydev.sem, 1);
  199. return 0;
  200. }
  201. void __exit mychar_exit(void)
  202. {
  203. dev_t devno = MKDEV(major, minor);
  204. cdev_del(&gmydev.mydev);
  205. unregister_chrdev_region(devno, mychar_num);
  206. }
  207. MODULE_LICENSE("GPL");
  208. module_init(mychar_init);
  209. module_exit(mychar_exit);

互斥锁:基于阻塞的互斥机制

a.初始化
struct mutex  my_mutex;
mutex_init(&my_mutex);

b.获取互斥体
void  mutex_lock(struct mutex *lock);

c.释放互斥体
void mutex_unlock(struct mutex *lock);

1. 定义对应类型的变量
2. 初始化对应变量

P/加锁
临界区
V/解锁


#include <linux/mutex.h>
 

适用场合:任务上下文之间且临界区执行时间较长时的互斥问题


示例代码(互斥锁)

mychar.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/wait.h>
  6. #include <linux/sched.h>
  7. #include <linux/poll.h>
  8. #include <linux/uaccess.h>
  9. #include "mychar.h"
  10. #define BUF_LEN 100
  11. int major = 11;
  12. int minor = 0;
  13. int mychar_num = 1;
  14. struct mychar_dev
  15. {
  16. struct cdev mydev;
  17. char mydev_buf[BUF_LEN];
  18. int curlen;
  19. struct mutex lock;
  20. wait_queue_head_t rq;
  21. wait_queue_head_t wq;
  22. struct fasync_struct* pasync_obj;
  23. };
  24. struct mychar_dev gmydev;
  25. int mychar_open(struct inode* pnode, struct file* pfile)
  26. {
  27. pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
  28. return 0;
  29. }
  30. int mychar_close(struct inode* pnode, struct file* pfile)
  31. {
  32. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  33. if (pmydev->pasync_obj != NULL) {
  34. fasync_helper(-1, pfile, 0, &pmydev->pasync_obj);
  35. }
  36. return 0;
  37. }
  38. ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
  39. {
  40. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  41. int size = 0;
  42. int ret = 0;
  43. mutex_lock(&pmydev->lock);
  44. if (pmydev->curlen <= 0) {
  45. if (pfile->f_flags & O_NONBLOCK) {
  46. // 非阻塞
  47. mutex_unlock(&pmydev->lock);
  48. printk("O_NONBLOCK No Data Read\n");
  49. return -1;
  50. } else {
  51. // 阻塞
  52. mutex_unlock(&pmydev->lock);
  53. ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
  54. if (ret) {
  55. printk("Wake up by signal\n");
  56. return -ERESTARTSYS;
  57. }
  58. mutex_lock(&pmydev->lock);
  59. }
  60. }
  61. if (count > pmydev->curlen) {
  62. size = pmydev->curlen;
  63. } else {
  64. size = count;
  65. }
  66. ret = copy_to_user(puser, pmydev->mydev_buf, size);
  67. if (ret) {
  68. mutex_unlock(&pmydev->lock);
  69. printk("copy_to_user failed\n");
  70. return -1;
  71. }
  72. memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);
  73. pmydev->curlen -= size;
  74. mutex_unlock(&pmydev->lock);
  75. wake_up_interruptible(&pmydev->wq);
  76. return size;
  77. }
  78. ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
  79. {
  80. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  81. int size = 0;
  82. int ret = 0;
  83. mutex_lock(&pmydev->lock);
  84. if (pmydev->curlen >= BUF_LEN) {
  85. if (pfile->f_flags & O_NONBLOCK) {
  86. mutex_unlock(&pmydev->lock);
  87. printk("O_NONBLOCK Can not write data\n");
  88. return -1;
  89. } else {
  90. mutex_unlock(&pmydev->lock);
  91. ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
  92. if (ret) {
  93. printk("Wake up by signal\n");
  94. return -ERESTARTSYS;
  95. }
  96. mutex_lock(&pmydev->lock);
  97. }
  98. }
  99. if (count > BUF_LEN - pmydev->curlen) {
  100. size = BUF_LEN - pmydev->curlen;
  101. } else {
  102. size = count;
  103. }
  104. ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
  105. if (ret) {
  106. mutex_unlock(&pmydev->lock);
  107. printk("copy_from_user failed\n");
  108. return -1;
  109. }
  110. pmydev->curlen += size;
  111. mutex_unlock(&pmydev->lock);
  112. wake_up_interruptible(&pmydev->rq);
  113. if (pmydev->pasync_obj != NULL) {
  114. kill_fasync(&pmydev->pasync_obj, SIGIO, POLL_IN);
  115. }
  116. return size;
  117. }
  118. long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
  119. {
  120. int __user *pret = (int*)arg;
  121. int maxlen = BUF_LEN;
  122. int ret = 0;
  123. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  124. switch (cmd) {
  125. case MYCHAR_IOCTL_GET_MAXLEN:
  126. ret = copy_to_user(pret, &maxlen, sizeof(int));
  127. if (ret) {
  128. printk("copy_to_user MAXLEN failed\n");
  129. return -1;
  130. }
  131. break;
  132. case MYCHAR_IOCTL_GET_CURLEN:
  133. mutex_lock(&pmydev->lock);
  134. ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
  135. mutex_unlock(&pmydev->lock);
  136. if (ret) {
  137. printk("copy_to_user CURLEN failed\n");
  138. return -1;
  139. }
  140. break;
  141. default:
  142. printk("The cmd is unknow\n");
  143. return -1;
  144. }
  145. return 0;
  146. }
  147. unsigned int mychar_poll(struct file* pfile, poll_table* ptb)
  148. {
  149. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  150. unsigned int mask = 0;
  151. poll_wait(pfile, &pmydev->rq, ptb);
  152. poll_wait(pfile, &pmydev->wq, ptb);
  153. mutex_lock(&pmydev->lock);
  154. if (pmydev->curlen > 0) {
  155. mask |= POLLIN | POLLRDNORM;
  156. }
  157. if (pmydev->curlen < BUF_LEN) {
  158. mask |= POLLOUT | POLLWRNORM;
  159. }
  160. mutex_unlock(&pmydev->lock);
  161. return mask;
  162. }
  163. int mychar_fasync(int fd, struct file* pfile, int mode) {
  164. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  165. return fasync_helper(fd, pfile, mode, &pmydev->pasync_obj);
  166. }
  167. struct file_operations myops = {
  168. .owner = THIS_MODULE,
  169. .open = mychar_open,
  170. .release = mychar_close,
  171. .read = mychar_read,
  172. .write = mychar_write,
  173. .unlocked_ioctl = mychar_ioctl,
  174. .poll = mychar_poll,
  175. .fasync = mychar_fasync,
  176. };
  177. int __init mychar_init(void)
  178. {
  179. int ret = 0;
  180. dev_t devno = MKDEV(major, minor);
  181. /* 申请设备号 */
  182. ret = register_chrdev_region(devno, mychar_num, "mychar");
  183. if (ret) {
  184. ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
  185. if (ret) {
  186. printk("get devno failed\n");
  187. return -1;
  188. }
  189. major = MAJOR(devno);
  190. }
  191. /* 给struct cdev对象指定操作函数集 */
  192. cdev_init(&gmydev.mydev, &myops);
  193. /* 给struct cdev对象添加到内核对应的数据结构里 */
  194. gmydev.mydev.owner = THIS_MODULE;
  195. cdev_add(&gmydev.mydev, devno, mychar_num);
  196. init_waitqueue_head(&gmydev.rq);
  197. init_waitqueue_head(&gmydev.wq);
  198. mutex_init(&gmydev.lock);
  199. return 0;
  200. }
  201. void __exit mychar_exit(void)
  202. {
  203. dev_t devno = MKDEV(major, minor);
  204. cdev_del(&gmydev.mydev);
  205. unregister_chrdev_region(devno, mychar_num);
  206. }
  207. MODULE_LICENSE("GPL");
  208. module_init(mychar_init);
  209. module_exit(mychar_exit);

选择并发控制机制的原则

1. 不允许睡眠的上下文需要采用忙等待类,可以睡眠的上下文可以采用阻塞类。在异常上下文中访问的竞争资源一定采用忙等待类。
2. 临界区操作较长的应用建议采用阻塞类,临界区很短的操作建议采用忙等待类。
3. 中断屏蔽仅在有与中断上下文共享资源时使用。
4. 共享资源仅是一个简单整型量时用原子变量


内核定时器

时钟中断

硬件有一个时钟装置,该装置每隔一定时间发出一个时钟中断(称为一次时钟嘀嗒-tick),对应的中断处理程序就将全局变量jiffies_64加1

jiffies_64   是一个全局64位整型, jiffies全局变量为其低32位的全局变量,程序中一般用jiffies

HZ:可配置的宏,表示1秒钟产生的时钟中断次数,一般设为100或200

延时机制

1. 短延迟:忙等待

  1. 1. void ndelay(unsigned long nsecs)
  2. 2. void udelay(unsigned long usecs)
  3. 3. void mdelay(unsigned long msecs)

2. 长延迟:忙等待

   使用jiffies比较宏来实现

  1. time_after(a,b)    //a > b
  2. time_before(a,b)   //a < b
  3.    
  4. // 延迟100个jiffies
  5. unsigned long delay = jiffies + 100;
  6. while(time_before(jiffies,delay))
  7. {
  8. ;
  9. }
  10.    
  11. // 延迟2s
  12. unsigned long delay = jiffies + 2*HZ;
  13. while(time_before(jiffies,delay))
  14. {
  15. ;
  16. }

3. 睡眠延迟----阻塞类

  1. void msleep(unsigned int msecs);
  2. unsigned long msleep_interruptible(unsigned int msecs);

延时机制的选择原则:

1. 异常上下文中只能采用忙等待类
2. 任务上下文短延迟采用忙等待类,长延迟采用阻塞类

定时器

(1)定义定时器结构体

  1. struct timer_list 
  2. {
  3. struct list_head entry;
  4.     unsigned long expires;  // 期望的时间值 jiffies + x * HZ
  5.     void (*function)(unsigned long); // 时间到达后,执行的回调函数,软中断异常上下文
  6.     unsigned long data;
  7. };

(2)初始化定时器 

init_timer(struct timer_list *)

(3)增加定时器 ------ 定时器开始计时

void add_timer(struct timer_list *timer);

(4)删除定时器 -------定时器停止工作

int del_timer(struct timer_list * timer);

(5)修改定时器 

 int mod_timer(struct timer_list *timer, unsigned long expires);

参考代码模板

  1. 定义struct timer_list tl类型的变量
  2. init_timer(...);//模块入口函数
  3. //模块入口函数或open或希望定时器开始工作的地方
  4. tl.expires = jiffies + n * HZ //n秒
  5. tl.function = xxx_func;
  6. tl.data = ...;
  7. add_timer(....);
  8. //不想让定时器继续工作时
  9. del_timer(....);
  10. void xxx_func(unsigned long arg)
  11. {
  12.     ......
  13.     mod_timer(....);//如需要定时器继续隔指定时间再次调用本函数
  14. }

内核内存管理

内核内存管理框架

内核将物理内存等分成N块4KB,称之为一页,每页都用一个struct page来表示,采用伙伴关系算法维护

内核地址空间划分图:

3G~3G+896M:低端内存,直接映射  虚拟地址 = 3G + 物理地址
细分为:ZONE_DMA、ZONE_NORMAL
分配方式:
        1. kmalloc:小内存分配,slab算法
        2. get_free_page:整页分配,2的n次方页,n最大为10

大于3G+896M:高端内存
细分为:vmalloc区、持久映射区、固定映射区
分配方式:vmalloc:虚拟地址连续,物理地址不连续

内核中常用动态分配

kmalloc

函数原型:

  1. void *kmalloc(size_t size, gfp_t flags);
  2. void *kzalloc(size_t size, gfp_t flags); // 分配内存同时置零

kmalloc() 申请的内存位于直接映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。 
   
较常用的 flags(分配内存的方法):

GFP_ATOMIC —— 分配内存的过程是一个原子过程,分配内存的过程不会被(高优先级进程或中断)打断;
GFP_KERNEL —— 正常分配内存;
GFP_DMA —— 给 DMA 控制器分配内存,需要使用该标志(DMA要求分配虚拟地址和物理地址连续)。

flags 的参考用法: 
 |– 进程上下文,可以睡眠      GFP_KERNEL 
 |– 异常上下文,不可以睡眠     GFP_ATOMIC 
 |  |– 中断处理程序          GFP_ATOMIC 
 |  |– 软中断            GFP_ATOMIC 
 |  |– Tasklet            GFP_ATOMIC 
 |– 用于DMA的内存,可以睡眠   GFP_DMA | GFP_KERNEL 
 |– 用于DMA的内存,不可以睡眠  GFP_DMA |GFP_ATOMIC 
   
对应的内存释放函数为:

void kfree(const void *objp);

vmalloc

void *vmalloc(unsigned long size);

vmalloc() 函数则会在虚拟内存空间给出一块连续的内存区,但这片连续的虚拟内存在物理内存中并不一定连续。由于 vmalloc() 没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就需要用此函数了。

对应的内存释放函数为:

void vfree(const void *addr);

注意:vmalloc() 和 vfree() 可以睡眠,因此不能从异常上下文调用。


kmalloc & vmalloc 的比较

kmalloc()、kzalloc()、vmalloc() 的共同特点是:

1. 用于申请内核空间的内存;
2. 内存以字节为单位进行分配;
3. 所分配的内存虚拟地址上连续;

kmalloc()、kzalloc()、vmalloc() 的区别是:

1. kzalloc 是强制清零的 kmalloc 操作;(以下描述不区分 kmalloc 和 kzalloc)
2. kmalloc 分配的内存大小有限制(128KB),而 vmalloc 没有限制;
3. kmalloc 可以保证分配的内存物理地址是连续的,但是 vmalloc 不能保证;
4. kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC),而 vmalloc 分配内存时则可能产生阻塞;
5. kmalloc 分配内存的开销小,因此 kmalloc 比 vmalloc 要快;

一般情况下,内存只有在要被 DMA 访问的时候才需要物理上连续,但为了性能上的考虑,内核中一般使用 kmalloc(),而只有在需要获得大块内存时才使用 vmalloc()。

分配选择原则:

1. 小内存(< 128k)用kmalloc,大内存用vmalloc或get_free_page
2. 如果需要比较大的内存,并且要求使用效率较高时用get_free_page,否则用vmalloc


示例代码(互斥锁的代码中设备结构体改用指针并用kmalloc分配内存)

mychar.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/wait.h>
  6. #include <linux/sched.h>
  7. #include <linux/poll.h>
  8. #include <linux/mm.h>
  9. #include <linux/slab.h>
  10. #include <asm/uaccess.h>
  11. #include "mychar.h"
  12. #define BUF_LEN 100
  13. int major = 11;
  14. int minor = 0;
  15. int mychar_num = 1;
  16. struct mychar_dev
  17. {
  18. struct cdev mydev;
  19. char mydev_buf[BUF_LEN];
  20. int curlen;
  21. struct mutex lock;
  22. wait_queue_head_t rq;
  23. wait_queue_head_t wq;
  24. struct fasync_struct* pasync_obj;
  25. };
  26. struct mychar_dev *pgmydev = NULL;
  27. int mychar_open(struct inode* pnode, struct file* pfile)
  28. {
  29. pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
  30. return 0;
  31. }
  32. int mychar_close(struct inode* pnode, struct file* pfile)
  33. {
  34. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  35. if (pmydev->pasync_obj != NULL) {
  36. fasync_helper(-1, pfile, 0, &pmydev->pasync_obj);
  37. }
  38. return 0;
  39. }
  40. ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
  41. {
  42. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  43. int size = 0;
  44. int ret = 0;
  45. mutex_lock(&pmydev->lock);
  46. if (pmydev->curlen <= 0) {
  47. if (pfile->f_flags & O_NONBLOCK) {
  48. // 非阻塞
  49. mutex_unlock(&pmydev->lock);
  50. printk("O_NONBLOCK No Data Read\n");
  51. return -1;
  52. } else {
  53. // 阻塞
  54. mutex_unlock(&pmydev->lock);
  55. ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
  56. if (ret) {
  57. printk("Wake up by signal\n");
  58. return -ERESTARTSYS;
  59. }
  60. mutex_lock(&pmydev->lock);
  61. }
  62. }
  63. if (count > pmydev->curlen) {
  64. size = pmydev->curlen;
  65. } else {
  66. size = count;
  67. }
  68. ret = copy_to_user(puser, pmydev->mydev_buf, size);
  69. if (ret) {
  70. mutex_unlock(&pmydev->lock);
  71. printk("copy_to_user failed\n");
  72. return -1;
  73. }
  74. memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);
  75. pmydev->curlen -= size;
  76. mutex_unlock(&pmydev->lock);
  77. wake_up_interruptible(&pmydev->wq);
  78. return size;
  79. }
  80. ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
  81. {
  82. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  83. int size = 0;
  84. int ret = 0;
  85. mutex_lock(&pmydev->lock);
  86. if (pmydev->curlen >= BUF_LEN) {
  87. if (pfile->f_flags & O_NONBLOCK) {
  88. mutex_unlock(&pmydev->lock);
  89. printk("O_NONBLOCK Can not write data\n");
  90. return -1;
  91. } else {
  92. mutex_unlock(&pmydev->lock);
  93. ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
  94. if (ret) {
  95. printk("Wake up by signal\n");
  96. return -ERESTARTSYS;
  97. }
  98. mutex_lock(&pmydev->lock);
  99. }
  100. }
  101. if (count > BUF_LEN - pmydev->curlen) {
  102. size = BUF_LEN - pmydev->curlen;
  103. } else {
  104. size = count;
  105. }
  106. ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
  107. if (ret) {
  108. mutex_unlock(&pmydev->lock);
  109. printk("copy_from_user failed\n");
  110. return -1;
  111. }
  112. pmydev->curlen += size;
  113. mutex_unlock(&pmydev->lock);
  114. wake_up_interruptible(&pmydev->rq);
  115. if (pmydev->pasync_obj != NULL) {
  116. kill_fasync(&pmydev->pasync_obj, SIGIO, POLL_IN);
  117. }
  118. return size;
  119. }
  120. long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
  121. {
  122. int __user *pret = (int*)arg;
  123. int maxlen = BUF_LEN;
  124. int ret = 0;
  125. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  126. switch (cmd) {
  127. case MYCHAR_IOCTL_GET_MAXLEN:
  128. ret = copy_to_user(pret, &maxlen, sizeof(int));
  129. if (ret) {
  130. printk("copy_to_user MAXLEN failed\n");
  131. return -1;
  132. }
  133. break;
  134. case MYCHAR_IOCTL_GET_CURLEN:
  135. mutex_lock(&pmydev->lock);
  136. ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
  137. mutex_unlock(&pmydev->lock);
  138. if (ret) {
  139. printk("copy_to_user CURLEN failed\n");
  140. return -1;
  141. }
  142. break;
  143. default:
  144. printk("The cmd is unknow\n");
  145. return -1;
  146. }
  147. return 0;
  148. }
  149. unsigned int mychar_poll(struct file* pfile, poll_table* ptb)
  150. {
  151. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  152. unsigned int mask = 0;
  153. poll_wait(pfile, &pmydev->rq, ptb);
  154. poll_wait(pfile, &pmydev->wq, ptb);
  155. mutex_lock(&pmydev->lock);
  156. if (pmydev->curlen > 0) {
  157. mask |= POLLIN | POLLRDNORM;
  158. }
  159. if (pmydev->curlen < BUF_LEN) {
  160. mask |= POLLOUT | POLLWRNORM;
  161. }
  162. mutex_unlock(&pmydev->lock);
  163. return mask;
  164. }
  165. int mychar_fasync(int fd, struct file* pfile, int mode) {
  166. struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
  167. return fasync_helper(fd, pfile, mode, &pmydev->pasync_obj);
  168. }
  169. struct file_operations myops = {
  170. .owner = THIS_MODULE,
  171. .open = mychar_open,
  172. .release = mychar_close,
  173. .read = mychar_read,
  174. .write = mychar_write,
  175. .unlocked_ioctl = mychar_ioctl,
  176. .poll = mychar_poll,
  177. .fasync = mychar_fasync,
  178. };
  179. int __init mychar_init(void)
  180. {
  181. int ret = 0;
  182. dev_t devno = MKDEV(major, minor);
  183. /* 申请设备号 */
  184. ret = register_chrdev_region(devno, mychar_num, "mychar");
  185. if (ret) {
  186. ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
  187. if (ret) {
  188. printk("get devno failed\n");
  189. return -1;
  190. }
  191. major = MAJOR(devno);
  192. }
  193. pgmydev = (struct mychar_dev*)kmalloc(sizeof(struct mychar_dev), GFP_KERNEL);
  194. if (NULL == pgmydev) {
  195. unregister_chrdev_region(devno, mychar_num);
  196. printk("kmalloc for struct mychar_dev failed\n");
  197. return -1;
  198. }
  199. /* 给struct cdev对象指定操作函数集 */
  200. cdev_init(&pgmydev->mydev, &myops);
  201. /* 给struct cdev对象添加到内核对应的数据结构里 */
  202. pgmydev->mydev.owner = THIS_MODULE;
  203. cdev_add(&pgmydev->mydev, devno, mychar_num);
  204. init_waitqueue_head(&pgmydev->rq);
  205. init_waitqueue_head(&pgmydev->wq);
  206. mutex_init(&pgmydev->lock);
  207. return 0;
  208. }
  209. void __exit mychar_exit(void)
  210. {
  211. dev_t devno = MKDEV(major, minor);
  212. cdev_del(&pgmydev->mydev);
  213. unregister_chrdev_region(devno, mychar_num);
  214. kfree(pgmydev);
  215. pgmydev = NULL;
  216. }
  217. MODULE_LICENSE("GPL");
  218. module_init(mychar_init);
  219. module_exit(mychar_exit);

IO访问:访问外设控制器的寄存器

两种方式:

1. IO端口:X86上用IO指令访问
2. IO内存:外设寄存器在SOC芯片手册上都有相应物理地址

IO内存访问接口:

  1. static inline void __iomem *ioremap(unsigned long offset, unsigned long size)
  2. /*
  3. 功能:实现IO管脚的映射
  4. 参数:offset:该管脚的偏移地址
  5. Size:该管脚映射空间的大小
  6. 返回值:成功返回映射的虚拟地址,失败NULL
  7. */
  8. static inline void iounmap(volatile void __iomem *addr)
  9. /*
  10. 功能:解除io管脚的映射
  11. 参数:addr:io管脚映射的地址
  12. */
  13. unsigned readb(void *addr); // 1字节 或ioread8(void *addr)
  14. unsigned readw(void *addr); // 2字节 或ioread16(void *addr)
  15. unsigned readl(void *addr); // 4字节 或ioread32(void *addr)
  16. /*
  17. 功能:读取寄存器的值
  18. 参数:addr 地址
  19. 返回值:读到的数据
  20. */
  21. void writeb(unsigned value, void *addr); // 1字节 或iowrite8(u8 value, void *addr)
  22. void writew(unsigned value, void *addr); // 2字节 或iowrite16(u16 value, void *addr)
  23. void writel(unsigned value, void *addr); // 4字节 或iowrite32(u32 value, void *addr)
  24. /*
  25. 功能:向指定的寄存器中,写入数据。
  26. 参数:value:待写入寄存器中的数据
  27. Address:寄存器的虚拟地址
  28. */

LED驱动

原理图

SOC芯片手册

GPX2_7 led2  GPX2CON----0x11000C40---28~31-----0001      GPX2DAT----0x11000C44-----7

GPX1_0 led3  GPX1CON----0x11000C20---    0~3-----0001      GPX1DAT----0x11000C24-----0

GPF3_4 led4  GPF3CON----0x114001E0--- 16~19-----0001      GPF3DAT----0x114001E4-----4

GPF3_5 led5  GPF3CON----0x114001E0--- 20~23-----0001      GPF3DAT----0x114001E4-----5

编写驱动步骤

a. 设计设备数据类型

b. 考虑需要支持的函数

c. 模块入口:ioremap + 设置成输出

d. 模块出口:iounmap

e. 编写关灯函数和开灯函数,实现ioctl


示例代码(LED驱动)

leddrv.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/wait.h>
  6. #include <linux/sched.h>
  7. #include <linux/poll.h>
  8. #include <linux/slab.h>
  9. #include <linux/mm.h>
  10. #include <linux/io.h>
  11. #include <asm/uaccess.h>
  12. #include <asm/atomic.h>
  13. #include "leddrv.h"
  14. #define GPX1CON 0x11000C20
  15. #define GPX1DAT 0x11000C24
  16. #define GPX2CON 0x11000C40
  17. #define GPX2DAT 0x11000C44
  18. #define GPF3CON 0x114001E0
  19. #define GPF3DAT 0x114001E4
  20. int major = 11;
  21. int minor = 0;
  22. int myled_num = 1;
  23. struct myled_dev
  24. {
  25. struct cdev mydev;
  26. volatile unsigned long *pled2_con;
  27. volatile unsigned long *pled2_dat;
  28. volatile unsigned long *pled3_con;
  29. volatile unsigned long *pled3_dat;
  30. volatile unsigned long *pled4_con;
  31. volatile unsigned long *pled4_dat;
  32. volatile unsigned long *pled5_con;
  33. volatile unsigned long *pled5_dat;
  34. };
  35. struct myled_dev *pgmydev = NULL;
  36. int myled_open(struct inode *pnode, struct file *pfile)
  37. {
  38. pfile->private_data =(void*)(container_of(pnode->i_cdev,struct myled_dev,mydev));
  39. return 0;
  40. }
  41. int myled_close(struct inode *pnode, struct file *pfile)
  42. {
  43. return 0;
  44. }
  45. void led_on(struct myled_dev *pmydev, int ledno)
  46. {
  47. switch (ledno) {
  48. case 2:
  49. writel(readl(pmydev->pled2_dat) | (0x1 << 7), pmydev->pled2_dat);
  50. break;
  51. case 3:
  52. writel(readl(pmydev->pled3_dat) | (0x1 << 0), pmydev->pled3_dat);
  53. break;
  54. case 4:
  55. writel(readl(pmydev->pled4_dat) | (0x1 << 4), pmydev->pled4_dat);
  56. break;
  57. case 5:
  58. writel(readl(pmydev->pled5_dat) | (0x1 << 5), pmydev->pled5_dat);
  59. break;
  60. }
  61. }
  62. void led_off(struct myled_dev *pmydev, int ledno)
  63. {
  64. switch (ledno) {
  65. case 2:
  66. writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)), pmydev->pled2_dat);
  67. break;
  68. case 3:
  69. writel(readl(pmydev->pled3_dat) & (~(0x1 << 0)), pmydev->pled3_dat);
  70. break;
  71. case 4:
  72. writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)), pmydev->pled4_dat);
  73. break;
  74. case 5:
  75. writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)), pmydev->pled5_dat);
  76. break;
  77. }
  78. }
  79. long myled_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
  80. {
  81. struct myled_dev *pmydev = (struct myled_dev*)pfile->private_data;
  82. if (arg < 2 || arg > 5) {
  83. return -1;
  84. }
  85. switch (cmd) {
  86. case MY_LED_ON:
  87. led_on(pmydev, arg);
  88. break;
  89. case MY_LED_OFF:
  90. led_off(pmydev, arg);
  91. break;
  92. default:
  93. return -1;
  94. }
  95. return 0;
  96. }
  97. struct file_operations myops = {
  98. .owner = THIS_MODULE,
  99. .open = myled_open,
  100. .release = myled_close,
  101. .unlocked_ioctl = myled_ioctl,
  102. };
  103. void ioremap_ledreg(struct myled_dev *pmydev)
  104. {
  105. pmydev->pled2_con = ioremap(GPX2CON, 4);
  106. pmydev->pled2_dat = ioremap(GPX2DAT, 4);
  107. pmydev->pled3_con = ioremap(GPX1CON, 4);
  108. pmydev->pled3_dat = ioremap(GPX1DAT, 4);
  109. pmydev->pled4_con = ioremap(GPF3CON, 4);
  110. pmydev->pled4_dat = ioremap(GPF3DAT, 4);
  111. pmydev->pled5_con = pmydev->pled4_con;
  112. pmydev->pled5_dat = pmydev->pled4_dat;
  113. }
  114. void iounmap_ledreg(struct myled_dev *pmydev)
  115. {
  116. iounmap(pmydev->pled2_con);
  117. iounmap(pmydev->pled2_dat);
  118. pmydev->pled2_con = NULL;
  119. pmydev->pled2_dat = NULL;
  120. iounmap(pmydev->pled3_con);
  121. iounmap(pmydev->pled3_dat);
  122. pmydev->pled3_con = NULL;
  123. pmydev->pled3_dat = NULL;
  124. iounmap(pmydev->pled4_con);
  125. iounmap(pmydev->pled4_dat);
  126. pmydev->pled4_con = NULL;
  127. pmydev->pled4_dat = NULL;
  128. pmydev->pled5_con = NULL;
  129. pmydev->pled5_dat = NULL;
  130. }
  131. void set_output_ledconreg(struct myled_dev *pmydev)
  132. {
  133. writel((readl(pmydev->pled2_con) & (~(0xF << 28))) | (0x1 << 28), pmydev->pled2_con);
  134. writel((readl(pmydev->pled3_con) & (~(0xF << 0))) | (0x1 << 0), pmydev->pled3_con);
  135. writel((readl(pmydev->pled4_con) & (~(0xF << 16))) | (0x1 << 16), pmydev->pled4_con);
  136. writel((readl(pmydev->pled5_con) & (~(0xF << 20))) | (0x1 << 20), pmydev->pled5_con);
  137. writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)), pmydev->pled2_dat);
  138. writel(readl(pmydev->pled3_dat) & (~(0x1 << 0)), pmydev->pled3_dat);
  139. writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)), pmydev->pled4_dat);
  140. writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)), pmydev->pled5_dat);
  141. }
  142. int __init myled_init(void)
  143. {
  144. int ret = 0;
  145. dev_t devno = MKDEV(major, minor);
  146. /* 申请设备号 */
  147. ret = register_chrdev_region(devno, myled_num, "myled");
  148. if (ret) {
  149. ret = alloc_chrdev_region(&devno, minor, myled_num, "myled");
  150. if (ret) {
  151. printk("get devno failed\n");
  152. return -1;
  153. }
  154. major = MAJOR(devno);
  155. }
  156. pgmydev = (struct myled_dev*)kmalloc(sizeof(struct myled_dev), GFP_KERNEL);
  157. if (NULL == pgmydev) {
  158. unregister_chrdev_region(devno, myled_num);
  159. printk("kmalloc failed\n");
  160. return -1;
  161. }
  162. memset(pgmydev, 0, sizeof(struct myled_dev));
  163. /* 给struct cdev对象指定操作函数集 */
  164. cdev_init(&pgmydev->mydev, &myops);
  165. /* 给struct cdev对象添加到内核对应的数据结构里 */
  166. pgmydev->mydev.owner = THIS_MODULE;
  167. cdev_add(&pgmydev->mydev, devno, myled_num);
  168. /* ioremap */
  169. ioremap_ledreg(pgmydev);
  170. /* con-register set output */
  171. set_output_ledconreg(pgmydev);
  172. return 0;
  173. }
  174. void __exit myled_exit(void)
  175. {
  176. dev_t devno = MKDEV(major, minor);
  177. /* iounmap */
  178. iounmap_ledreg(pgmydev);
  179. cdev_del(&pgmydev->mydev);
  180. unregister_chrdev_region(devno, myled_num);
  181. kfree(pgmydev);
  182. pgmydev = NULL;
  183. }
  184. MODULE_LICENSE("GPL");
  185. module_init(myled_init);
  186. module_exit(myled_exit);

leddrv.h

  1. #ifndef LED_DRIVER_H
  2. #define LED_DRIVER_H
  3. #define LED_DEV_MAGIC 'g'
  4. #define MY_LED_OFF _IO(LED_DEV_MAGIC, 0)
  5. #define MY_LED_ON _IO(LED_DEV_MAGIC, 1)
  6. #endif

testled.c

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <sys/ioctl.h>
  5. #include <unistd.h>
  6. #include <stdio.h>
  7. #include "leddrv.h"
  8. int main(int argc, char *argv[])
  9. {
  10. int fd = -1;
  11. int onoff = 0;
  12. int no = 0;
  13. if (argc < 4) {
  14. printf("The argument is too few\n");
  15. return 1;
  16. }
  17. sscanf(argv[2], "%d", &onoff);
  18. sscanf(argv[3], "%d", &no);
  19. if (no < 2 || no > 5) {
  20. printf("led-no is invalid\n");
  21. return 2;
  22. }
  23. fd = open(argv[1], O_RDONLY);
  24. if (fd < 0) {
  25. printf("open %s failed\n", argv[1]);
  26. return 3;
  27. }
  28. if (onoff) {
  29. ioctl(fd, MY_LED_ON, no);
  30. } else {
  31. ioctl(fd, MY_LED_OFF, no);
  32. }
  33. close(fd);
  34. fd = -1;
  35. return 0;
  36. }

测试结果

ubuntu上将 leddrv.c 编译成 arm 架构的 ko 文件,将 testled.c 编译成 arm 架构的可执行文件,再将这两个文件拷贝到挂载的根文件系统目录下

  1. shrek@ubuntu16:~/share/mydrivercode$ make ARCH=arm
  2. shrek@ubuntu16:~/share/mydrivercode$ arm-none-linux-gnueabi-gcc ./testled.c -o testled
  3. shrek@ubuntu16:~/share/mydrivercode$ cp leddrv.ko /opt/4412/rootfs/
  4. shrek@ubuntu16:~/share/mydrivercode$ cp testled /opt/4412/rootfs/

开发板上挂载好根文件系统,执行如下命令

  1. [root@farsight]#insmod leddrv.ko
  2. [root@farsight]#cat /proc/devices | grep led
  3. 11 myled
  4. [root@farsight]#mknod /dev/leddev c 11 0
  5. [root@farsight]#./testled /dev/leddev 1 2
  6. [root@farsight]#./testled /dev/leddev 1 3
  7. [root@farsight]#./testled /dev/leddev 1 4
  8. [root@farsight]#./testled /dev/leddev 1 5
  9. [root@farsight]#./testled /dev/leddev 0 5
  10. [root@farsight]#./testled /dev/leddev 0 4
  11. [root@farsight]#./testled /dev/leddev 0 3
  12. [root@farsight]#./testled /dev/leddev 0 2

看到开发板上的LED灯可以控制亮灭


设备树

起源

减少垃圾代码
减轻驱动开发工作量
驱动代码和设备信息分离
参考Open Firmware设计
用来记录硬件平台中各种硬件设备的属性信息

基本组成

两种源文件:

1. xxxxx.dts dts是device tree source的缩写
2. xxxxx.dtsi dtsi是device tree source include的缩写,意味着这样源文件用于被dts文件包含用

基本语法

dts文件主体内容由多个节点组成
每个节点可以包含0或多个子节点,形成树状关系
每个dts文件都有一个根节点,其它节点都是它的子孙
根节点一般来描述整个开发板硬件平台,其它节点用来表示具体设备、总线的属性信息
各个节点可以有多个属性,每个属性用key-value键值对来表示

节点语法:

  1. [label:] node-name[@unit-address] {
  2. [properties definitions];
  3. [child nodes];
  4. };
  5. []表示可选项
  6. label:可选项,节点别名,为了缩短节点访问路径,后续节点中可以使用 &label 来表示引用指定节点
  7. node-name:节点名
  8. unit-address:可选项,设备地址,一般填写该设备寄存器组或内存块的首地址
  9. properties definitions:可选项,属性定义
  10. child nodes:可选项,子节点

属性语法:

  1. [label:] property-name = value;
  2. [label:] property-name;
  3. 属性可以无值
  4. 有值的属性,可以有三种取值:
  5. 1. arrays of cells(1个或多个32位数据, 64位数据使用232位数据表示,空格分隔),用尖括号表示(< >)
  6. 2. string(字符串), 用双引号表示(" ")
  7. 3. bytestring(1个或多个字节,空格分隔),用方括号表示([])
  8. 4. 用,分隔的多值

特殊节点

根节点

根节点表示整块开发板的信息

  1. #address-cells // 在子节点的reg属性中, 使用多少个u32整数来描述地址(address)
  2. #size-cells // 在子节点的reg属性中, 使用多少个u32整数来描述大小(size)
  3. compatible // 定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备,即描述其兼容哪些平台
  4. model // 比如有2款板子配置基本一致, 它们的compatible是一样的,那么就通过model来分辨这2款板子

/memory

所有设备树文件的必需节点,它定义了系统物理内存的 layout

  1. device_type = "memory";
  2. reg //用来指定内存的地址、大小

/chosen

传递内核启动时使用的参数parameter

bootargs  //字符串,内核启动参数, 跟u-boot中设置的bootargs作用一样

/cpus  多核CPU支持

/cpus节点下有1个或多个cpu子节点,cpu子节点中用reg属性用来标明自己是哪一个cpu

所以 /cpus 中有以下2个属性:

  1. #address-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
  2. #size-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size) 必须设置为0

常用属性

phandle

数字形式的节点标识,在后续节点中属性值性质表示某节点时,可以引用对应节点

如:

  1. pic@10000000 {
  2. phandle = <1>;
  3. interrupt-controller;
  4. };
  5. another-device-node {
  6. interrupt-parent = <1>; // 使用phandle值为1来引用上述节点
  7. };

地址  ---  重要

reg属性:表示内存区域region,语法:

reg = <address1 length1 [address2 length2] [address3 length3]>;

#address-cells:reg属性中, 使用多少个u32整数来描述地址(address),语法:

#address-cells = <数字>;

#size-cells:reg属性中, 使用多少个u32整数来描述大小(size),语法:

#size-cells = <数字>;

compatible  ---  重要

驱动和设备(设备节点)的匹配依据,compatible(兼容性)的值可以有不止一个字符串以满足不同的需求,语法:

compatible = "字符串1","字符串2",...;

中断  ---  重要

a. 中断控制器节点用的属性:

interrupt-controller 一个无值空属性用来声明这个node接收中断信号,表示该节点是一个中断控制器

#interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符

b. 中断源设备节点用的属性:

interrupt-parent:标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的,语法:

interrupt-parent = <引用某中断控制器节点>

interrupts 一个中断标识符列表,表示每一个中断输出信号,语法:

  1. interrupts = <中断号 触发方式>
  2. 1 low-to-high 上升沿触发
  3. 2 high-to-low 下降沿触发
  4. 4 high level 高电平触发
  5. 8 low level 低电平触发

gpio  ---  重要

gpio也是最常见的IO口,常用的属性有:

a. 对于GPIO控制器:

gpio-controller,无值空属性,用来说明该节点描述的是一个gpio控制器

#gpio-cells,用来表示要用几个cell描述一个 GPIO引脚

b. 对于GPIO使用者节点:

gpio使用节点的属性

  1. xxx-gpio = <&引用GPIO控制器 GPIO标号 工作模式>
  2. 工作模式:
  3. 1 低电平有效 GPIO_ACTIVE_HIGH
  4. 0 高电平有效 GPIO_ACTIVE_LOW

属性设置套路

一般来说,每一种设备的节点属性设置都会有一些套路,比如可以设置哪些属性?属性值怎么设置?那怎么知道这些套路呢,有两种思路:

1. 抄类似的dts,比如我们自己项目的平台是4412,那么就可以抄exynos4412-tiny4412.dts、exynos4412-smdk4412.dts这类相近的dts
2. 查询内核中的文档,比如Documentation/devicetree/bindings/i2c/i2c-imx.txt就描述了imx平台的i2c属性设置方法;Documentation/devicetree/bindings/fb就描述了lcd、lvds这类属性设置方法

常用接口

struct device_node  对应设备树中的一个节点
struct property 对应节点中一个属性

of_find_node_by_path

  1. /*
  2. include/of.h
  3. of_find_node_by_path - 通过路径查找指定节点
  4. @path - 带全路径的节点名,也可以是节点的别名
  5. 成功:得到节点的首地址;失败:NULL
  6. */
  7. struct device_node * of_find_node_by_path(const char *path);

of_find_property

  1. /*
  2. include/of.h
  3. of_find_property - 提取指定属性的值
  4. @np - 设备节点指针
  5. @name - 属性名称
  6. @lenp - 属性值的字节数
  7. 成功:属性值的首地址;失败:NULL
  8. */
  9. struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);

of_get_named_gpio

  1. /*
  2.  * include/of_gpio.h
  3.  * of_get_named_gpio - 从设备树中提取gpio口
  4.  * @np - 设备节点指针
  5.  * @propname - 属性名
  6.  * @index - gpio口引脚标号 
  7.  * 成功:得到GPIO口编号;失败:负数,绝对值是错误码
  8.  */
  9. int of_get_named_gpio(struct device_node *np, const char *propname, int index);

irq_of_parse_and_map

  1. /*
  2. 功能:获得设备树中的中断号并进行映射
  3. 参数:node:设备节点
  4. index:序号
  5. 返回值:成功:中断号 失败:错误码
  6. */
  7. unsigned int irq_of_parse_and_map(struct device_node *node, int index)

读属性值

  1. /*
  2. of_property_read_string - 提取字符串(属性值)
  3. @np - 设备节点指针
  4. @propname - 属性名称
  5. @out_string - 输出参数,指向字符串(属性值)
  6. 成功:0;失败:负数,绝对值是错误码
  7. */
  8. int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);

读数值

  1. int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value)
  2. int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value)
  3. int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)

判断属性是否存在

int of_property_read_bool(const struct device_node *np,const char *propname)

读数组

int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_value,size_t sz)

GPIO接口

向内核申请GPIO

int gpio_request(unsigned gpio,const char *label) 

功能:其实就是让内核检查一下该GPIO引脚是否被其它设备占用,如果没有占用则返回0并用label做一下标记,表示被本设备占用,否则返回负数

void gpio_free(unsigned gpio)

功能:去除本设备对该GPIO的占用标记,表示本设备向内核归还对该GPIO引脚的使用权,此后其它设备可占用该GPIO引脚

设置GPIO方向

  1. int gpio_direction_input(unsigned gpio)
  2. int gpio_direction_output(unsigned gpio,int value)

读写GPIO数据

  1. int gpio_get_value(unsigned gpio)
  2. int gpio_set_value(unsigned gpio,int value)

LED驱动设备树版

1. 在设备树源文件的根节点下添加本设备的节点(该节点中包含本设备用到的资源信息)

   ..../linux3.14/arch/arm/boot/dts/exynos4412-fs4412.dts

  1. fs4412-leds {
  2.     compatible = "4412,led2-5";
  3.     led2-gpio = <&gpx2 7 0>;
  4.     led3-gpio = <&gpx1 0 0>;
  5.     led4-gpio = <&gpf3 4 0>;
  6.     led5-gpio = <&gpf3 5 0>;
  7. };

 2. 在linux内核源码的顶层目录下执行:make dtbs  (生成对应的dtb文件)

 3. cp   ?????.dtb   /tftpboot

 4. 编写驱动代码:

    a. 通过本设备在设备树中的路径找到对应节点(struct device_node类型的地址值)

    b. 调用 of_get_named_gpio 函数得到某个GPIO的编号

    c. struct leddev结构体中记录所有用到的GPIO编号

    d. 使用某个GPIO引脚前需先通过gpio_request函数向内核申请占用该引脚,不用该引脚时可通过gpio_free归还给内核

    e. 通过gpio_direction_input和gpio_direction_output函数来设置某个GPIO的作用

    f. 通过gpio_get_value函数可以获取某个GPIO引脚的当前电平

    g. 通过gpio_set_value函数可以改变某个GPIO引脚的电平


示例代码(LED驱动,设备树版)

leddrv.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/gpio.h>
  5. #include <linux/of_gpio.h>
  6. #include <linux/cdev.h>
  7. #include <linux/wait.h>
  8. #include <linux/sched.h>
  9. #include <linux/poll.h>
  10. #include <linux/slab.h>
  11. #include <linux/mm.h>
  12. #include <linux/io.h>
  13. #include <asm/uaccess.h>
  14. #include <asm/atomic.h>
  15. #include "leddrv.h"
  16. int major = 11;
  17. int minor = 0;
  18. int myled_num = 1;
  19. struct myled_dev
  20. {
  21. struct cdev mydev;
  22. unsigned int led2gpio;
  23. unsigned int led3gpio;
  24. unsigned int led4gpio;
  25. unsigned int led5gpio;
  26. };
  27. struct myled_dev *pgmydev = NULL;
  28. int myled_open(struct inode *pnode, struct file *pfile)
  29. {
  30. pfile->private_data =(void*)(container_of(pnode->i_cdev,struct myled_dev,mydev));
  31. return 0;
  32. }
  33. int myled_close(struct inode *pnode, struct file *pfile)
  34. {
  35. return 0;
  36. }
  37. void led_on(struct myled_dev *pmydev, int ledno)
  38. {
  39. switch (ledno) {
  40. case 2:
  41. gpio_set_value(pmydev->led2gpio, 1);
  42. break;
  43. case 3:
  44. gpio_set_value(pmydev->led3gpio, 1);
  45. break;
  46. case 4:
  47. gpio_set_value(pmydev->led4gpio, 1);
  48. break;
  49. case 5:
  50. gpio_set_value(pmydev->led5gpio, 1);
  51. break;
  52. }
  53. }
  54. void led_off(struct myled_dev *pmydev, int ledno)
  55. {
  56. switch (ledno) {
  57. case 2:
  58. gpio_set_value(pmydev->led2gpio, 0);
  59. break;
  60. case 3:
  61. gpio_set_value(pmydev->led3gpio, 0);
  62. break;
  63. case 4:
  64. gpio_set_value(pmydev->led4gpio, 0);
  65. break;
  66. case 5:
  67. gpio_set_value(pmydev->led5gpio, 0);
  68. break;
  69. }
  70. }
  71. long myled_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
  72. {
  73. struct myled_dev *pmydev = (struct myled_dev*)pfile->private_data;
  74. if (arg < 2 || arg > 5) {
  75. return -1;
  76. }
  77. switch (cmd) {
  78. case MY_LED_ON:
  79. led_on(pmydev, arg);
  80. break;
  81. case MY_LED_OFF:
  82. led_off(pmydev, arg);
  83. break;
  84. default:
  85. return -1;
  86. }
  87. return 0;
  88. }
  89. struct file_operations myops = {
  90. .owner = THIS_MODULE,
  91. .open = myled_open,
  92. .release = myled_close,
  93. .unlocked_ioctl = myled_ioctl,
  94. };
  95. void request_leds_gpio(struct myled_dev *pmydev, struct device_node *pnode) // 申请占用设备
  96. {
  97. pmydev->led2gpio = of_get_named_gpio(pnode, "led2-gpio", 0);
  98. gpio_request(pmydev->led2gpio, "led2");
  99. pmydev->led3gpio = of_get_named_gpio(pnode, "led3-gpio", 0);
  100. gpio_request(pmydev->led3gpio, "led3");
  101. pmydev->led4gpio = of_get_named_gpio(pnode, "led4-gpio", 0);
  102. gpio_request(pmydev->led4gpio, "led4");
  103. pmydev->led5gpio = of_get_named_gpio(pnode, "led5-gpio", 0);
  104. gpio_request(pmydev->led5gpio, "led5");
  105. }
  106. void set_leds_gpio_output(struct myled_dev *pmydev) // 配置成输出
  107. {
  108. gpio_direction_output(pmydev->led2gpio, 0);
  109. gpio_direction_output(pmydev->led3gpio, 0);
  110. gpio_direction_output(pmydev->led4gpio, 0);
  111. gpio_direction_output(pmydev->led5gpio, 0);
  112. }
  113. void free_leds_gpio(struct myled_dev *pmydev) // free设备
  114. {
  115. gpio_free(pmydev->led2gpio);
  116. gpio_free(pmydev->led3gpio);
  117. gpio_free(pmydev->led4gpio);
  118. gpio_free(pmydev->led5gpio);
  119. }
  120. int __init myled_init(void)
  121. {
  122. int ret = 0;
  123. dev_t devno = MKDEV(major, minor);
  124. struct device_node *pnode = NULL;
  125. pnode = of_find_node_by_path("/fs4412-leds"); // 设备树中的节点名
  126. if (NULL == pnode) {
  127. printk("find node by path failed\n");
  128. return -1;
  129. }
  130. ret = register_chrdev_region(devno, myled_num, "myled");
  131. if (ret) {
  132. ret = alloc_chrdev_region(&devno, minor, myled_num, "myled");
  133. if (ret) {
  134. printk("get devno failed\n");
  135. return -1;
  136. }
  137. major = MAJOR(devno);
  138. }
  139. pgmydev = (struct myled_dev*)kmalloc(sizeof(struct myled_dev), GFP_KERNEL);
  140. if (NULL == pgmydev) {
  141. unregister_chrdev_region(devno, myled_num);
  142. printk("kmalloc failed\n");
  143. return -1;
  144. }
  145. memset(pgmydev, 0, sizeof(struct myled_dev));
  146. cdev_init(&pgmydev->mydev, &myops);
  147. pgmydev->mydev.owner = THIS_MODULE;
  148. cdev_add(&pgmydev->mydev, devno, myled_num);
  149. /* ioremap */
  150. request_leds_gpio(pgmydev, pnode);
  151. /* con-register set output */
  152. set_leds_gpio_output(pgmydev);
  153. return 0;
  154. }
  155. void __exit myled_exit(void)
  156. {
  157. dev_t devno = MKDEV(major, minor);
  158. /* iounmap */
  159. free_leds_gpio(pgmydev);
  160. cdev_del(&pgmydev->mydev);
  161. unregister_chrdev_region(devno, myled_num);
  162. kfree(pgmydev);
  163. pgmydev = NULL;
  164. }
  165. MODULE_LICENSE("GPL");
  166. module_init(myled_init);
  167. module_exit(myled_exit);

leddrv.h

  1. #ifndef LED_DRIVER_H
  2. #define LED_DRIVER_H
  3. #define LED_DEV_MAGIC 'g'
  4. #define MY_LED_OFF _IO(LED_DEV_MAGIC, 0)
  5. #define MY_LED_ON _IO(LED_DEV_MAGIC, 1)
  6. #endif

testled.c

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <sys/ioctl.h>
  5. #include <unistd.h>
  6. #include <stdio.h>
  7. #include "leddrv.h"
  8. int main(int argc, char *argv[])
  9. {
  10. int fd = -1;
  11. int onoff = 0;
  12. int no = 0;
  13. if (argc < 4) {
  14. printf("The argument is too few\n");
  15. return 1;
  16. }
  17. sscanf(argv[2], "%d", &onoff);
  18. sscanf(argv[3], "%d", &no);
  19. if (no < 2 || no > 5) {
  20. printf("led-no is invalid\n");
  21. return 2;
  22. }
  23. fd = open(argv[1], O_RDONLY);
  24. if (fd < 0) {
  25. printf("open %s failed\n", argv[1]);
  26. return 3;
  27. }
  28. if (onoff) {
  29. ioctl(fd, MY_LED_ON, no);
  30. } else {
  31. ioctl(fd, MY_LED_OFF, no);
  32. }
  33. close(fd);
  34. fd = -1;
  35. return 0;
  36. }

测试结果

ubuntu上将 leddrv.c 编译成 arm 架构的 ko 文件,将 testled.c 编译成 arm 架构的可执行文件,再将这两个文件拷贝到挂载的根文件系统目录下

  1. shrek@ubuntu16:~/share/mydrivercode$ make ARCH=arm
  2. shrek@ubuntu16:~/share/mydrivercode$ arm-none-linux-gnueabi-gcc ./testled.c -o testled
  3. shrek@ubuntu16:~/share/mydrivercode$ cp leddrv.ko /opt/4412/rootfs/
  4. shrek@ubuntu16:~/share/mydrivercode$ cp testled /opt/4412/rootfs/

开发板上挂载好根文件系统,执行如下命令

  1. [root@farsight]#insmod leddrv.ko
  2. [root@farsight]#cat /proc/devices | grep led
  3. 11 myled
  4. [root@farsight]#mknod /dev/leddev c 11 0
  5. [root@farsight]#./testled /dev/leddev 1 2
  6. [root@farsight]#./testled /dev/leddev 1 3
  7. [root@farsight]#./testled /dev/leddev 1 4
  8. [root@farsight]#./testled /dev/leddev 1 5
  9. [root@farsight]#./testled /dev/leddev 0 5
  10. [root@farsight]#./testled /dev/leddev 0 4
  11. [root@farsight]#./testled /dev/leddev 0 3
  12. [root@farsight]#./testled /dev/leddev 0 2

看到开发板上的LED灯可以控制亮灭


中断处理

什么是中断

一种硬件上的通知机制,用来通知CPU发生了某种需要立即处理的事件

分为:

1. 内部中断  CPU执行程序的过程中,发生的一些硬件出错、运算出错事件(如分母为0、溢出等等),不可屏蔽
2. 外部中断  外设发生某种情况,通过一个引脚的高、低电平变化来通知CPU (如外设产生了数据、某种处理完毕等等)


中断处理原理

任何一种中断产生,CPU都会暂停当前执行的程序,跳转到内存固定位置执行一段程序,该程序被称为总的中断服务程序,在该程序中区分中断源,然后进一步调用该中断源对应的处理函数。

中断源对应的处理函数被称为分中断处理程序,一般每一个分中断处理程序对应一个外设产生的中断

写驱动时,如果外设有中断,则需要编写一个函数(分中断处理程序)来处理这种中断


中断接口

中断申请 

  1. int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
  2. /*
  3. 参数:
  4.     irq:所申请的中断号
  5.     handler:该中断号对应的中断处理函数
  6.     flags:中断触发方式或处理方式 
  7.         触发方式:IRQF_TRIGGER_NONE         //无触发
  8. IRQF_TRIGGER_RISING     //上升沿触发
  9. IRQF_TRIGGER_FALLING   //下降沿触发
  10. IRQF_TRIGGER_HIGH      //高电平触发
  11. IRQF_TRIGGER_LOW         //低电平触发
  12.         处理方式:
  13. IRQF_DISABLED        //用于快速中断,处理中屏蔽所有中断
  14. IRQF_SHARED          //共享中断
  15.         name:中断名 /proc/interrupts
  16.         dev:传递给中断例程的参数,共享中断时用于区分那个设备,一般为对应设备的结构体地址,无共享中断时写NULL
  17. 返回值:成功:0 失败:错误码
  18. */

中断释放

  1. void free_irq(unsigned int irq, void *dev_id)
  2. /*
  3. 功能:释放中断号
  4. 参数:
  5.     irq:设备号
  6.     dev_id:共享中断时用于区分那个设备一般强转成设备号,无共享中断时写NULL
  7. */

中断处理函数原型

  1. typedef irqreturn_t (*irq_handler_t)(int, void *);
  2. /*
  3. 参数:
  4.     int:中断号
  5.     void*:对应的申请中断时的dev_id
  6. 返回值:
  7.     typedef enum irqreturn irqreturn_t;    //中断返回值类型
  8.     enum irqreturn {
  9.         IRQ_NONE    = (0 << 0),
  10.         IRQ_HANDLED    = (1 << 0),
  11.         IRQ_WAKE_THREAD    = (1 << 1),
  12.     };
  13.     返回IRQ_HANDLED表示处理完了,返回IRQ_NONE在共享中断表示不处理
  14. */

按键驱动

exynos4412-fs4412.dts中增加节点

  1. mykey2_node {
  2.     compatible = "mykey2,key2";
  3.     key2-gpio = <&gpx1 1 0>;
  4.     interrupt-parent = <&gpx1>;
  5.     interrupts = <1 3>;
  6. };

示例代码(按键驱动)

key.c

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/gpio.h>
  5. #include <linux/interrupt.h>
  6. #include <linux/of_gpio.h>
  7. #include <linux/of_irq.h>
  8. #include <linux/cdev.h>
  9. #include <linux/wait.h>
  10. #include <linux/sched.h>
  11. #include <linux/poll.h>
  12. #include <linux/mm.h>
  13. #include <linux/delay.h>
  14. #include <linux/slab.h>
  15. #include <asm/uaccess.h>
  16. #include "key.h"
  17. int major = 11;
  18. int minor = 0;
  19. int key2_num = 1;
  20. struct key2_dev
  21. {
  22. struct cdev mydev;
  23. int gpio; // GPIO号
  24. int irqno; // 中断号
  25. struct keyvalue data; // 按键产生的数据
  26. int newflag; // 按键产生标志
  27. spinlock_t lock;
  28. wait_queue_head_t rq;
  29. };
  30. struct key2_dev *pgmydev = NULL;
  31. int key2_open(struct inode* pnode, struct file* pfile)
  32. {
  33. pfile->private_data = (void *)(container_of(pnode->i_cdev, struct key2_dev, mydev));
  34. return 0;
  35. }
  36. int key2_close(struct inode* pnode, struct file* pfile)
  37. {
  38. return 0;
  39. }
  40. ssize_t key2_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
  41. {
  42. struct key2_dev *pmydev = (struct key2_dev *)pfile->private_data;
  43. int size = 0;
  44. int ret = 0;
  45. if (count < sizeof(struct keyvalue)) {
  46. printk("expect read size is invalid\n");
  47. return -1;
  48. }
  49. spin_lock(&pmydev->lock);
  50. if (!pmydev->newflag) {
  51. if (pfile->f_flags & O_NONBLOCK) {
  52. // 非阻塞
  53. spin_unlock(&pmydev->lock);
  54. printk("O_NONBLOCK No Data Read\n");
  55. return -1;
  56. } else {
  57. // 阻塞
  58. spin_unlock(&pmydev->lock);
  59. ret = wait_event_interruptible(pmydev->rq, pmydev->newflag == 1);
  60. if (ret) {
  61. printk("Wake up by signal\n");
  62. return -ERESTARTSYS;
  63. }
  64. spin_lock(&pmydev->lock);
  65. }
  66. }
  67. if (count > sizeof(struct keyvalue)) {
  68. size = sizeof(struct keyvalue);
  69. } else {
  70. size = count;
  71. }
  72. ret = copy_to_user(puser, &pmydev->data, size);
  73. if (ret) {
  74. spin_unlock(&pmydev->lock);
  75. printk("copy_to_user failed\n");
  76. return -1;
  77. }
  78. pmydev->newflag = 0;
  79. spin_unlock(&pmydev->lock);
  80. return size;
  81. }
  82. unsigned int key2_poll(struct file* pfile, poll_table* ptb)
  83. {
  84. struct key2_dev *pmydev = (struct key2_dev *)pfile->private_data;
  85. unsigned int mask = 0;
  86. poll_wait(pfile, &pmydev->rq, ptb);
  87. spin_lock(&pmydev->lock);
  88. if (pmydev->newflag) {
  89. mask |= POLLIN | POLLRDNORM;
  90. }
  91. spin_unlock(&pmydev->lock);
  92. return mask;
  93. }
  94. struct file_operations myops = {
  95. .owner = THIS_MODULE,
  96. .open = key2_open,
  97. .release = key2_close,
  98. .read = key2_read,
  99. .poll = key2_poll,
  100. };
  101. irqreturn_t key2_irq_handle(int no, void *arg)
  102. {
  103. struct key2_dev *pmydev = (struct key2_dev*)arg;
  104. int status1 = 0;
  105. int status2 = 0;
  106. int status = 0;
  107. status1 = gpio_get_value(pmydev->gpio);
  108. mdelay(1);
  109. status2 = gpio_get_value(pmydev->gpio);
  110. if (status1 != status2) {
  111. return IRQ_NONE;
  112. }
  113. status = status1;
  114. spin_lock(&pmydev->lock);
  115. if (status == pmydev->data.status) {
  116. spin_unlock(&pmydev->lock);
  117. return IRQ_NONE;