一、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; // 用于操作epoll
struct 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");