一、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多路复用的应用程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/epoll.h>
/* According to earlier standards */
#include <sys/time.h>int main(int argc, char const *argv[])
{int fd1, fd2, epfd;struct epoll_event event; // 用于操作epollstruct epoll_event events[10]; // 存放就绪事件描述符的数组char buf[128] = {0};// 创建epoll句柄epfd = epoll_create(1);if (epfd < 0){printf("epoll_create filed\n");exit(-1);}// 打开设备文件fd1 = open("/dev/input/mouse0", O_RDWR);if (fd1 < 0){printf("打开鼠标设备文件失败\n");exit(-1);}fd2 = open("/dev/mycdev0", O_RDWR);if (fd2 < 0){printf("打开鼠标设备文件失败\n");exit(-1);}// 添加准备就绪事件进入epoll;event.events = EPOLLIN; // 读事件event.data.fd = fd1;if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd1, &event) < 0){printf("epoll_ctl add filed\n");}event.events = EPOLLIN; // 读事件event.data.fd = fd2;if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd2, &event) < 0){printf("epoll_ctl add filed\n");}// 监听事件是否发生while (1){// 如果成功,ret接收返回的事件个数,把就绪的事件放在events数组中int ret = epoll_wait(epfd, events, 10, -1);if (ret < 0){printf("epoll_wait filed\n");exit(-1);}int i;// 循环遍历数组,做事件的处理for (i = 0; i < ret; i++){if (events[i].events & EPOLLIN)//判断发生的事件是不是读事件{read(events[i].data.fd, buf, sizeof(buf));printf("buf:%s\n", buf);}}}close(fd1);close(fd2);return 0;
}
信号驱动IO:
proc1.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/epoll.h>
/* According to earlier standards */
#include <sys/time.h>
int fd; // 存放就绪事件描述符的数组
char buf[128] = {0};
// 定义信号处理函数
void sigio_handler(int sig)
{read(fd, buf, sizeof(buf));printf("buf:%s\n", buf);
}
int main(int argc, char const *argv[])
{// 打开设备文件fd = open("/dev/myled0", O_RDWR);if (fd < 0){printf("打开设备文件失败\n");exit(-1);}// 注册SIGIO信号的信号处理函数signal(SIGIO, sigio_handler);// 回调驱动中的fasync方法,完成发送信号之前的准备工作int flags = fcntl(fd,F_GETFL); // 获取文件描述符属性fcntl(fd,F_SETFL,flags|FASYNC); // 在文件描述符表的flags中添加FASYNC,就可以回调fasync方法fcntl(fd,F_SETOWN,getpid());//驱动发送信号只发送给当前进程while(1){printf("aaaaa\n");sleep(1);}close(fd);return 0;
}
proc2.c
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>int main(int argc, char const *argv[])
{char buf[128] = "hello world";int fd = open("/dev/myled0", O_RDWR);if (fd < 0){printf("打开设备文件失败\n");exit(-1);}write(fd, buf, sizeof(buf));close(fd);return 0;
}
fasync.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/poll.h>
struct cdev *cdev;
char kbuf[128] = {0};
unsigned int major = 0;
unsigned int minor = 0;
dev_t devno;
module_param(major, uint, 0664); // 方便在命令行传递major的值
struct class *cls;
struct device *dev;
struct fasync_struct *fp; // 定义一个异步对象指针// 封装操作方法
int mycdev_open(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);return 0;
}
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{int ret;// 判断IO方式if (file->f_flags & O_NONBLOCK) // 非阻塞{}else // 阻塞{}ret = copy_to_user(ubuf, kbuf, size);if (ret){printk("copy_to_user err\n");return -EIO;}return 0;
}
ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
{int ret;// 从用户拷贝数据,模拟硬件数据ret = copy_from_user(kbuf, ubuf, size);if (ret){printk("copy_from_user err\n");return -EIO;}//发送信号(异步对象二级指针,要发生的信号,发送信号时添加事件的标志位)kill_fasync(&fp,SIGIO,POLL_IN);return 0;
}int mycdev_fasync(int fd, struct file *file, int on) // 异步操作方法
{// 完成发送信号之前的准备工作fasync_helper(fd, file, on, &fp);return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);return 0;
}
// 定义一个操作方法结构体对象并且初始化
struct file_operations fops = {.open = mycdev_open,.read = mycdev_read,.write = mycdev_write,.fasync = mycdev_fasync,.release = mycdev_close,
};
static int __init mycdev_init(void)
{int ret;// 为字符设备驱动对象申请空间cdev = cdev_alloc();if (cdev == NULL){printk("字符设备驱动对象申请空间失败\n");ret = -EFAULT;goto out1;}printk("申请对象空间成功\n");// 初始化字符设备驱动对象cdev_init(cdev, &fops);// 申请设备号if (major > 0) // 静态指定设备号{ret = register_chrdev_region(MKDEV(major, minor), 3, "myled");if (ret){printk("静态申请设备号失败\n");goto out2;}}else if (major == 0) // 动态申请设备号{ret = alloc_chrdev_region(&devno, minor, 3, "myled");if (ret){printk("动态申请设备号失败\n");goto out2;}major = MAJOR(devno); // 获取主设备号minor = MINOR(devno); // 获取次设备号}printk("申请设备号成功\n");// 注册字符设备驱动对象ret = cdev_add(cdev, MKDEV(major, minor), 3);if (ret){printk("注册字符设备驱动对象失败\n");goto out3;}printk("注册字符设备驱动对象成功\n");// 向上提交目录信息cls = class_create(THIS_MODULE, "myled");if (IS_ERR(cls)){printk("向上提交目录失败\n");ret = -PTR_ERR(cls);goto out4;}printk("向上提交目录成功\n");// 向上提交设备节点信息int i;for (i = 0; i < 3; i++){dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);if (IS_ERR(dev)){printk("向上提交设备节点信息失败\n");ret = -PTR_ERR(dev);goto out5;}}printk("向上提交设备信息成功\n");return 0;
out5:// 释放前一次提交成功的设备信息for (--i; i >= 0; i--){device_destroy(cls, MKDEV(major, i));}class_destroy(cls); // 释放目录
out4:cdev_del(cdev);
out3:unregister_chrdev_region(MKDEV(major, minor), 3);
out2:kfree(cdev);
out1:return ret;
}
static void __exit mycdev_exit(void)
{// 释放节点信息int i;for (i = 0; i < 3; i++){device_destroy(cls, MKDEV(major, i));}// 销毁目录class_destroy(cls);// 注销驱动对象cdev_del(cdev);// 释放设备号unregister_chrdev_region(MKDEV(major, minor), 3);// 释放对象空间kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");