• linux驱动开发day6--(epoll实现IO多路复用、信号驱动IO、设备树以及节点和属性解析相关API使用)


    一、IO多路复用--epoll实现

    1.核心:

    红黑树、一张表、三个接口

    2.实现过程及API(三个接口)

    1)创建epoll句柄/创建红黑树根节点

    int epfd=epoll_create(int size--无意义,>0即可)----------成功:返回根节点对应文件描述符,失败:-1

    2)将要监测的文件描述符挂载到红黑树上

    a.struct epoll_event event;定义事件结构体

    b.struct epoll_event events[10];定义存放就绪事件描述符的数组

    c.添加准备就绪事件进入epoll,如:

    event.events = EPOLLIN; // 读事件

    event.data.fd = fd1;

    epoll_ctl(epfd, EPOLL_CTL_ADD---控制方法, fd1, &event)

    3)监听事件是否发生,阻塞等待准备好的文件描述符

    epoll_wait(epfd, events, 10, -1--不关心是否超时);

    返回值:

    >0:准备好的文件描述符的个数

    =0:超时

    <0:失败

    4)遍历数组,做事件的处理

    二、信号驱动IO

    异步IO方式,linux预留了一个信号SIGIO用于进行信号驱动IO,当硬件数据准备就绪后会发起一个硬件中断,在中断的处理函数中向当前进程发送一个SIGIO信号。进程收到SIGIO信号后执行信号处理函数,在信号处理函数中将数据读走即可。

    1.实现过程及API

    应用程序:

    1)打开设备文件

    2)注册信号的信号处理函数--signal(SIGIO,信号处理函数)

    3)回调驱动中的fasync方法,完成发送信号之前的准备工作

            a.获取文件描述符属性

                    int flags=fcntl(fd,F_GETFL);

            b.在文件描述符表的flags中添加FASYNC

                    fcntl(fd,F_SETFL,flags|FASYNC);

            c.设置fd对应的驱动程序发送SIGIO信号只发送给当前进程

                    fcntl(fd,F_SETOWN,getpid());

    4)注意:不能让主程序结束

    驱动程序:

    1)定义一个异步对象指针

            struct fasync_struct *fp;

    2)异步操作方法

    int mycdev_fasync(int fd, struct file *file, int on) // 异步操作方法

    {

            // 完成发送信号之前的准备工作

            fasync_helper(fd, file, on, &fp);

            return 0;

    }

    需要在操作方法结构体对象中加     .fasync = mycdev_fasync,

    3)向进程发送信号

    参数: fp:异步对象的二级指针

                sig:要发生的信号 SIGIO

                band:发送信号时添加的事件标志          POLL_IN表述读数据操作

    //发送信号

    kill_fasync(&fp,SIGIO,POLL_IN);

    三、设备树

    1.概念

    1)设备树(DeviceTree/DT/of)是用来保存设备信息的一种树形结构

    2)设备树的源码是独立于linux内核源码存在的

    3)设备树上的设备信息在内核启动后被内核解析,加载到内核空间

    4)设备树上的节点(包含属性子节点保存设备的设备信息;设备的信息由多个属性(链表形式存在,属性是键值对共同描述.

    2.引入设备树的原因

            为了让驱动可以兼容更多硬件,不在驱动中指定设备信息,让驱动中获取设备树上的设备信息,基于这些设备信息完成硬件的控制

    设备树linux官方手册:Device Tree Usage - eLinux.org

    3.设备树节点结构体struct device_node和属性结构体 struct property

    4.设备树节点解析API

    1)根据设备树节点的名字解析指定的设备树节点信息

    struct device_node *dnode;

    dnode=of_find_node_by_name(NULL(默认从根节点解析),"mynode");

    返回值:成功返回目标节点首地址,失败返回NULL

    测试:

    printk("name=%s,value=%s\n",dnode->properties->name,(char *)dnode->properties->value);

    2)根据设备树节点路径解析设备树节点信息

    3)根据节点的厂商信息解析指定的节点

    dnode=of_find_compatible_node(NULL(默认从根节点解析),NULL(设备类型),:compatible值);

    4)将大端字节序32位的数据转换成主机字节序

    __u32 __be32_to_cpup(const __be32 *p)

    printk("name=%s,value=%x %x\n",dnode->properties->next->next->name,

            __be32_to_cpup((u32 *)dnode->properties->next->next->value),

            __be32_to_cpup((u32 *)dnode->properties->next->next->value+1));

    5.属性解析API

    返回值:成功返回属性对象指针,失败返回NULL

    struct property *pr;

    int len;

    pr=of_find_property(dnode,"uint",&len);

    测试:

    printk("name=%s value=%x %x\n",pr->name,__be32_to_cpup((u32 *)pr->value),

            __be32_to_cpup((u32 *)pr->value+1)); 

    epoll实现IO多路复用的应用程序:
    1. #include <stdio.h>
    2. #include <sys/types.h>
    3. #include <sys/stat.h>
    4. #include <fcntl.h>
    5. #include <unistd.h>
    6. #include <stdlib.h>
    7. #include <string.h>
    8. #include <sys/wait.h>
    9. #include <sys/ioctl.h>
    10. #include <sys/select.h>
    11. #include <sys/epoll.h>
    12. /* According to earlier standards */
    13. #include <sys/time.h>
    14. int main(int argc, char const *argv[])
    15. {
    16. int fd1, fd2, epfd;
    17. struct epoll_event event; // 用于操作epoll
    18. struct epoll_event events[10]; // 存放就绪事件描述符的数组
    19. char buf[128] = {0};
    20. // 创建epoll句柄
    21. epfd = epoll_create(1);
    22. if (epfd < 0)
    23. {
    24. printf("epoll_create filed\n");
    25. exit(-1);
    26. }
    27. // 打开设备文件
    28. fd1 = open("/dev/input/mouse0", O_RDWR);
    29. if (fd1 < 0)
    30. {
    31. printf("打开鼠标设备文件失败\n");
    32. exit(-1);
    33. }
    34. fd2 = open("/dev/mycdev0", O_RDWR);
    35. if (fd2 < 0)
    36. {
    37. printf("打开鼠标设备文件失败\n");
    38. exit(-1);
    39. }
    40. // 添加准备就绪事件进入epoll;
    41. event.events = EPOLLIN; // 读事件
    42. event.data.fd = fd1;
    43. if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd1, &event) < 0)
    44. {
    45. printf("epoll_ctl add filed\n");
    46. }
    47. event.events = EPOLLIN; // 读事件
    48. event.data.fd = fd2;
    49. if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd2, &event) < 0)
    50. {
    51. printf("epoll_ctl add filed\n");
    52. }
    53. // 监听事件是否发生
    54. while (1)
    55. {
    56. // 如果成功,ret接收返回的事件个数,把就绪的事件放在events数组中
    57. int ret = epoll_wait(epfd, events, 10, -1);
    58. if (ret < 0)
    59. {
    60. printf("epoll_wait filed\n");
    61. exit(-1);
    62. }
    63. int i;
    64. // 循环遍历数组,做事件的处理
    65. for (i = 0; i < ret; i++)
    66. {
    67. if (events[i].events & EPOLLIN)//判断发生的事件是不是读事件
    68. {
    69. read(events[i].data.fd, buf, sizeof(buf));
    70. printf("buf:%s\n", buf);
    71. }
    72. }
    73. }
    74. close(fd1);
    75. close(fd2);
    76. return 0;
    77. }
    信号驱动IO:

    proc1.c

    1. #include <stdio.h>
    2. #include <sys/types.h>
    3. #include <sys/stat.h>
    4. #include <fcntl.h>
    5. #include <unistd.h>
    6. #include <stdlib.h>
    7. #include <string.h>
    8. #include <sys/wait.h>
    9. #include <sys/ioctl.h>
    10. #include <sys/select.h>
    11. #include <sys/epoll.h>
    12. /* According to earlier standards */
    13. #include <sys/time.h>
    14. int fd; // 存放就绪事件描述符的数组
    15. char buf[128] = {0};
    16. // 定义信号处理函数
    17. void sigio_handler(int sig)
    18. {
    19. read(fd, buf, sizeof(buf));
    20. printf("buf:%s\n", buf);
    21. }
    22. int main(int argc, char const *argv[])
    23. {
    24. // 打开设备文件
    25. fd = open("/dev/myled0", O_RDWR);
    26. if (fd < 0)
    27. {
    28. printf("打开设备文件失败\n");
    29. exit(-1);
    30. }
    31. // 注册SIGIO信号的信号处理函数
    32. signal(SIGIO, sigio_handler);
    33. // 回调驱动中的fasync方法,完成发送信号之前的准备工作
    34. int flags = fcntl(fd,F_GETFL); // 获取文件描述符属性
    35. fcntl(fd,F_SETFL,flags|FASYNC); // 在文件描述符表的flags中添加FASYNC,就可以回调fasync方法
    36. fcntl(fd,F_SETOWN,getpid());//驱动发送信号只发送给当前进程
    37. while(1)
    38. {
    39. printf("aaaaa\n");
    40. sleep(1);
    41. }
    42. close(fd);
    43. return 0;
    44. }

    proc2.c

    1. #include <stdlib.h>
    2. #include <stdio.h>
    3. #include <sys/types.h>
    4. #include <sys/stat.h>
    5. #include <fcntl.h>
    6. #include <unistd.h>
    7. #include <string.h>
    8. #include <sys/ioctl.h>
    9. int main(int argc, char const *argv[])
    10. {
    11. char buf[128] = "hello world";
    12. int fd = open("/dev/myled0", O_RDWR);
    13. if (fd < 0)
    14. {
    15. printf("打开设备文件失败\n");
    16. exit(-1);
    17. }
    18. write(fd, buf, sizeof(buf));
    19. close(fd);
    20. return 0;
    21. }

    fasync.c

    1. #include <linux/init.h>
    2. #include <linux/module.h>
    3. #include <linux/cdev.h>
    4. #include <linux/fs.h>
    5. #include <linux/device.h>
    6. #include <linux/uaccess.h>
    7. #include <linux/slab.h>
    8. #include <linux/wait.h>
    9. #include <linux/poll.h>
    10. struct cdev *cdev;
    11. char kbuf[128] = {0};
    12. unsigned int major = 0;
    13. unsigned int minor = 0;
    14. dev_t devno;
    15. module_param(major, uint, 0664); // 方便在命令行传递major的值
    16. struct class *cls;
    17. struct device *dev;
    18. struct fasync_struct *fp; // 定义一个异步对象指针
    19. // 封装操作方法
    20. int mycdev_open(struct inode *inode, struct file *file)
    21. {
    22. printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    23. return 0;
    24. }
    25. ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
    26. {
    27. int ret;
    28. // 判断IO方式
    29. if (file->f_flags & O_NONBLOCK) // 非阻塞
    30. {
    31. }
    32. else // 阻塞
    33. {
    34. }
    35. ret = copy_to_user(ubuf, kbuf, size);
    36. if (ret)
    37. {
    38. printk("copy_to_user err\n");
    39. return -EIO;
    40. }
    41. return 0;
    42. }
    43. ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
    44. {
    45. int ret;
    46. // 从用户拷贝数据,模拟硬件数据
    47. ret = copy_from_user(kbuf, ubuf, size);
    48. if (ret)
    49. {
    50. printk("copy_from_user err\n");
    51. return -EIO;
    52. }
    53. //发送信号(异步对象二级指针,要发生的信号,发送信号时添加事件的标志位)
    54. kill_fasync(&fp,SIGIO,POLL_IN);
    55. return 0;
    56. }
    57. int mycdev_fasync(int fd, struct file *file, int on) // 异步操作方法
    58. {
    59. // 完成发送信号之前的准备工作
    60. fasync_helper(fd, file, on, &fp);
    61. return 0;
    62. }
    63. int mycdev_close(struct inode *inode, struct file *file)
    64. {
    65. printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    66. return 0;
    67. }
    68. // 定义一个操作方法结构体对象并且初始化
    69. struct file_operations fops = {
    70. .open = mycdev_open,
    71. .read = mycdev_read,
    72. .write = mycdev_write,
    73. .fasync = mycdev_fasync,
    74. .release = mycdev_close,
    75. };
    76. static int __init mycdev_init(void)
    77. {
    78. int ret;
    79. // 为字符设备驱动对象申请空间
    80. cdev = cdev_alloc();
    81. if (cdev == NULL)
    82. {
    83. printk("字符设备驱动对象申请空间失败\n");
    84. ret = -EFAULT;
    85. goto out1;
    86. }
    87. printk("申请对象空间成功\n");
    88. // 初始化字符设备驱动对象
    89. cdev_init(cdev, &fops);
    90. // 申请设备号
    91. if (major > 0) // 静态指定设备号
    92. {
    93. ret = register_chrdev_region(MKDEV(major, minor), 3, "myled");
    94. if (ret)
    95. {
    96. printk("静态申请设备号失败\n");
    97. goto out2;
    98. }
    99. }
    100. else if (major == 0) // 动态申请设备号
    101. {
    102. ret = alloc_chrdev_region(&devno, minor, 3, "myled");
    103. if (ret)
    104. {
    105. printk("动态申请设备号失败\n");
    106. goto out2;
    107. }
    108. major = MAJOR(devno); // 获取主设备号
    109. minor = MINOR(devno); // 获取次设备号
    110. }
    111. printk("申请设备号成功\n");
    112. // 注册字符设备驱动对象
    113. ret = cdev_add(cdev, MKDEV(major, minor), 3);
    114. if (ret)
    115. {
    116. printk("注册字符设备驱动对象失败\n");
    117. goto out3;
    118. }
    119. printk("注册字符设备驱动对象成功\n");
    120. // 向上提交目录信息
    121. cls = class_create(THIS_MODULE, "myled");
    122. if (IS_ERR(cls))
    123. {
    124. printk("向上提交目录失败\n");
    125. ret = -PTR_ERR(cls);
    126. goto out4;
    127. }
    128. printk("向上提交目录成功\n");
    129. // 向上提交设备节点信息
    130. int i;
    131. for (i = 0; i < 3; i++)
    132. {
    133. dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);
    134. if (IS_ERR(dev))
    135. {
    136. printk("向上提交设备节点信息失败\n");
    137. ret = -PTR_ERR(dev);
    138. goto out5;
    139. }
    140. }
    141. printk("向上提交设备信息成功\n");
    142. return 0;
    143. out5:
    144. // 释放前一次提交成功的设备信息
    145. for (--i; i >= 0; i--)
    146. {
    147. device_destroy(cls, MKDEV(major, i));
    148. }
    149. class_destroy(cls); // 释放目录
    150. out4:
    151. cdev_del(cdev);
    152. out3:
    153. unregister_chrdev_region(MKDEV(major, minor), 3);
    154. out2:
    155. kfree(cdev);
    156. out1:
    157. return ret;
    158. }
    159. static void __exit mycdev_exit(void)
    160. {
    161. // 释放节点信息
    162. int i;
    163. for (i = 0; i < 3; i++)
    164. {
    165. device_destroy(cls, MKDEV(major, i));
    166. }
    167. // 销毁目录
    168. class_destroy(cls);
    169. // 注销驱动对象
    170. cdev_del(cdev);
    171. // 释放设备号
    172. unregister_chrdev_region(MKDEV(major, minor), 3);
    173. // 释放对象空间
    174. kfree(cdev);
    175. }
    176. module_init(mycdev_init);
    177. module_exit(mycdev_exit);
    178. MODULE_LICENSE("GPL");

  • 相关阅读:
    maven私服搭建及应用
    《Go 简易速速上手小册》第9章:数据库交互(2024 最新版)
    Python的pip换源详解
    Word控件Spire.Doc 【表单域】教程(四):如何在 C#、VB.NET 中删除自定义属性字段
    算法---腐烂的橘子
    Alertmanager
    猿创征文 | 国产数据库TiDB架构特性
    【JavaScript】掌握BOM浏览器对象模型
    多变量时间序列生成模型GAN介绍与实现
    微信小程序上传图片到服务端,springboot项目。避免踩坑保姆教程
  • 原文地址:https://blog.csdn.net/m0_72852022/article/details/132909907