文章目录
1.IO模型
IO模型的种类
-
非阻塞
-
阻塞
-
IO多路复用
-
异步通知(信号驱动IO)
2.非阻塞IO模型
非阻塞:在使用open打开文件的时候需要或上O_NONBLOCK的宏,如果是非阻塞打开方式
在调用read函数读取数据的时候,不管底层的数据是否准备好都要立即返回到用户空间。
open("/dev/myled0",O_RDWR|O_NOBLOCK); //非阻塞方式打开
read(fd,buf,sizeof(buf));
---------------------------------------------------------------
mycdev_read()
{
if(file->f_flags &O_NONBLOCK){
//非阻塞
//从硬件中读取数据
//将数据拷贝到用户空间即可
}
}
3阻塞IO模型
3.1阻塞IO模型的原理
阻塞:用户使用open打开设备文件的时候,默认就是阻塞方式打开的。如果用户
调用read函数想要读取数据的时候,如果数据没有准备好,进程休眠。如果硬件的
数据准备好了就会产生硬件中断,在中断处理函数中唤醒这个休眠的进程即可。从
硬件中将数据读取到内核空间,然后调用copy_to_user将数据拷贝到用户空间即可。
fd = open("/dev/myled0",O_RDWR); //阻塞方式打开
//将阻塞打开方式改为非阻塞
//fcntl(fd,F_SETFL,fcntl(fd,F_GETFL)|O_NONBLOCK);
read(fd,buf,sizeof(buf));
---------------------------------------------------------------
mycdev_read()
{
if(file->f_flags &O_NONBLOCK){
//非阻塞
}else{
//阻塞(进程休眠)
}
//从硬件中读取数据
//将数据拷贝到用户空间
}
中断处理函数中:
唤醒休眠的进程
3.2阻塞IO模型的API
1.定义等待队列头
wait_queue_head_t wq;
2.初始化等待队列头
init_waitqueue_head(&wq);
3.阻塞(定义等待队列项,将等待队列项放到等待队列结尾,进程休眠)
wait_event(wq, condition) //不可中断的等待态
功能:让进程进入不可中断的等待态
参数:
@wq:等待队列头
@condition:数据是否准备好的条件
//0数据没有准备好进程需要休眠 ,真代表数据准备好了,进程不需要休眠。
返回值:无
wait_event_interruptible(wq, condition) //可中断的等待态
功能:让进程进入可中断的等待态
参数:
@wq:等待队列头
@condition:数据是否准备好的条件
//0数据没有准备好进程需要休眠 ,真代表数据准备好了,进程不需要休眠。
返回值:如果是数据准备好了返回0,如果是信号唤醒的休眠返回错误码-ERESTARTSYS
4.唤醒
condition=1;
wake_up(&wq)
wake_up_interruptible(&wq)
3.3阻塞IO模型实例
B唤醒A进程的方式
myled.c
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define CNAME "myled"
#define COUNT 3
struct cdev* cdev;
int major = 238;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
//定义等待队列头
wait_queue_head_t wq;
//数据是否准备好的条件,0没有准备好,1准备好了
int condition=0;
int myled_open(struct inode* inode, struct file* filp)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t myled_read(struct file* filp,
char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
//判断打开的方式
if(filp->f_flags & O_NONBLOCK){
//非阻塞
return -EINVAL;
}else{
//阻塞
ret = wait_event_interruptible(wq,condition);
if(ret == -ERESTARTSYS){
printk("receive signal....\n");
return ret;
}
}
//将数据拷贝到用户空间
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_to_user(ubuf, kbuf, size);
if (ret) {
printk("copy data to user error\n");
return -EINVAL;
}
//将condition设置为0,为后面调用考虑
condition=0;
return size;
}
ssize_t myled_write(struct file* filp,
const char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if (ret) {
printk("copy data from user error\n");
return -EINVAL;
}
//如果condition不设置为1,唤醒之后又会休眠
condition=1;
wake_up_interruptible(&wq);
return size;
}
int myled_close(struct inode* inode, struct file* filp)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
const struct file_operations fops = {
.open = myled_open,
.read = myled_read,
.write = myled_write,
.release = myled_close,
};
static int __init myled_init(void)
{
int ret, i;
dev_t devno;
// 1.分配对象
cdev = cdev_alloc();
if (cdev == NULL) {
printk("alloc cdev memory error\n");
ret = -ENOMEM;
goto ERR1;
}
// 2.对象初始化
cdev_init(cdev, &fops);
// 3.设备号的申请
if (major == 0) {
//动态申请
ret = alloc_chrdev_region(&devno, minor, COUNT, CNAME);
if (ret) {
printk("dynamic:request device number error\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
} else {
//静态指定
ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
if (ret) {
printk("static:request device number error\n");
goto ERR2;
}
}
// 4.字符设备驱动的注册
ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
if (ret) {
printk("register chardev error\n");
goto ERR3;
}
// 5.自动创建设备节点
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls)) {
printk("class create error\n");
ret = PTR_ERR(cls);
goto ERR4;
}
for (i = minor; i < minor + 3; i++) {
dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);
if (IS_ERR(dev)) {
printk("device create error\n");
ret = PTR_ERR(dev);
goto ERR5;
}
}
//初始化等待队列头
init_waitqueue_head(&wq);
return 0; /*!!!!!!!!!!!!!!不要忘记!!!!!!!!!!!!!!!!!!!!!!!*/
ERR5:
for (--i; i >= minor; i--) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit myled_exit(void)
{
int i;
//注销设备节点
for (i = minor; i < minor + 3; i++) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
//字符设备驱动的注销
cdev_del(cdev);
//设备号释放
unregister_chrdev_region(MKDEV(major, minor), COUNT);
//释放cdev的内存空间
kfree(cdev);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
test.c
#include <head.h>
int main(int argc, const char* argv[])
{
int fd;
pid_t pid;
char buf[128] = "i am test block code...............\n";
if ((fd = open("/dev/myled0", O_RDWR)) == -1)
PRINT_ERR("open error");
pid = fork();
if (pid == -1) {
PRINT_ERR("fork error");
} else if (pid == 0) {
//子进程读数据
while (1) {
memset(buf, 0, sizeof(buf));
read(fd, buf, sizeof(buf));
printf("child buf = %s", buf);
}
} else {
while (1) {
sleep(3);
write(fd, buf, sizeof(buf));
}
wait(NULL);
}
close(fd);
return 0;
}
3.4阻塞IO模型中阻塞的实现流程
//wait_event_interruptible如果数据没有准备好让进程进入休眠状态
//wq_head:等待队列头
//condition:数据是否准备好的条件
#define wait_event_interruptible(wq_head, condition)
({
int __ret = 0;
//如果用户传递的condition为0,对0取反就是真,就会执行if语句里面的内容
//如果用户传递的condition为真,此时if语句就不成立
if (!(condition))
__ret = __wait_event_interruptible(wq_head, condition);
__ret; //如果数据准备好了这个宏返回的是0
})
#define __wait_event_interruptible(wq_head, condition)
___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0,schedule())
//wq_head:等待队列头
//condition:数据是否准备好的条件
//TASK_INTERRUPTIBLE:进程的状态,可中断的等待态
//0,0
//schedule();主动放弃cpu
#define ___wait_event(wq_head, condition, state, exclusive, ret, cmd)
({
struct wait_queue_entry __wq_entry;
//等待队列项,__wq_entry等待队列项的变量
long __ret = ret; /* explicit shadow */
init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0);
//对等待队列项进程初始化
for (;;) {
long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);
//将等待队列项放到等待队列头之后
//将进程的状态修改为可中断的等待态
//set_current_state(TASK_INTERRUPTIBLE);
if (condition) //如果数据准备好condition为真从这里退出
break;
if ( signal_pending_state(state, current))
//如果收到了信号,这里的if为真,从这里退出
{
list_del_init(&wq_entry->entry);
__ret = -ERESTARTSYS;
goto __out;
}
schedule();
//如果上述的两个条件都不为真,执行schedule()函数,主动放弃cpu
//直到收到信号或者调用唤醒的函数才回到这里继续执行
}
finish_wait(&wq_head, &__wq_entry);
//set_current_state(TASK_RUNNING); 将进程的状态重新设置为运行态
//将等待队列项从等待队列头上删除
__out: __ret;
})
4.IO多路复用
4.1IO多路复用原理简介
如果在同一个app应用程序想同时监听多个硬件的数据,如果使用阻塞的IO模型会出现如下效果:
当read(fd1,buf1,sizeof(buf1))调用的使用由于si7006数据没有准备好进程会休眠,而此时
鼠标的数据准备准备好了,要去读取鼠标的数据,但是此时read(fd2,buf2,sizeof(buf2))调用
不到。所以就会出现数据准备好没有取读的现象。可通过IO多路复用的机制来解决。
IO多路复用:将打开的文件描述符fd1和fd2可以放到select监听的表中,让select监听
这两个文件描述符,如果文件描述符的数据没有准备好,进程休眠。如果有其中一个或
多个硬件的数据准备好,就会产生中断,在中断处理函数中唤醒这个休眠的进程。select
就返回了。借助 FD_ISSET(int fd, fd_set *set)判断当前的文件描述符数据是否准备好
了如果数据准备好了调用read将数据读走即可。
4.2IO多路复用接口调用过程
us:
fd1 = open("/dev/mycdev0",O_RDWR);
fd2 = open("/dev/input/mouse0",O_RDWR);
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd1,&rfds);
FD_SET(fd2,&rfds);
select(fd2+1,&rfds,NULL,NULL,NULL); //select/poll/epoll
if(FD_ISSET(fd1,&rfds)){
read(fd1,buf1,sizeof(buf1));
}
if(FD_ISSET(fd2,&rfds)){
read(fd2,buf2,sizeof(buf2));
}
______________________________________________________________________________
ks:fops://应用成的select、poll、epoll调用的时候驱动的poll函数都会执行
#include <linux/poll.h>
__poll_t (*poll) (struct file *file, struct poll_table_struct *wait)
{
//1.定义返回值的变量
__poll_t mask=0;
//2.猜测是和阻塞相关的函数
poll_wait(file,等待队列头,wait);
//3.如果数据准备好
if(condition){
mask |= EPOLLIN ; //EPOLLIN 可读 EPOLLOUT 可写
}
//4.返回结果
return mask;
}
grep ".poll = " * -nR
4.3IO多路复用的实例
mycdev.c
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#define CNAME "mycdev"
#define COUNT 3
struct cdev* cdev;
int major = 238;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
//定义等待队列头
wait_queue_head_t wq;
//数据是否准备好的条件,0没有准备好,1准备好了
int condition = 0;
int mycdev_open(struct inode* inode, struct file* filp)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t mycdev_read(struct file* filp,
char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
//将数据拷贝到用户空间
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_to_user(ubuf, kbuf, size);
if (ret) {
printk("copy data to user error\n");
return -EINVAL;
}
//将condition设置为0,为后面调用考虑
condition = 0;
return size;
}
ssize_t mycdev_write(struct file* filp,
const char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if (ret) {
printk("copy data from user error\n");
return -EINVAL;
}
//如果condition不设置为1,唤醒之后又会休眠
condition = 1;
wake_up_interruptible(&wq);
return size;
}
__poll_t mycdev_poll(struct file* file,
struct poll_table_struct* wait)
{
// 1.定义返回值的变量
__poll_t mask = 0;
// 2.猜测是和阻塞相关的函数
poll_wait(file, &wq, wait);
// 3.如果数据准备好
if (condition) {
mask |= EPOLLIN; // EPOLLIN 可读 EPOLLOUT 可写
}
// 4.返回结果
return mask;
}
int mycdev_close(struct inode* inode, struct file* filp)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
const struct file_operations fops = {
.open = mycdev_open,
.read = mycdev_read,
.write = mycdev_write,
.poll = mycdev_poll,
.release = mycdev_close,
};
static int __init mycdev_init(void)
{
int ret, i;
dev_t devno;
// 1.分配对象
cdev = cdev_alloc();
if (cdev == NULL) {
printk("alloc cdev memory error\n");
ret = -ENOMEM;
goto ERR1;
}
// 2.对象初始化
cdev_init(cdev, &fops);
// 3.设备号的申请
if (major == 0) {
//动态申请
ret = alloc_chrdev_region(&devno, minor, COUNT, CNAME);
if (ret) {
printk("dynamic:request device number error\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
} else {
//静态指定
ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
if (ret) {
printk("static:request device number error\n");
goto ERR2;
}
}
// 4.字符设备驱动的注册
ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
if (ret) {
printk("register chardev error\n");
goto ERR3;
}
// 5.自动创建设备节点
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls)) {
printk("class create error\n");
ret = PTR_ERR(cls);
goto ERR4;
}
for (i = minor; i < minor + 3; i++) {
dev = device_create(cls, NULL, MKDEV(major, i), NULL, "mycdev%d", i);
if (IS_ERR(dev)) {
printk("device create error\n");
ret = PTR_ERR(dev);
goto ERR5;
}
}
//初始化等待队列头
init_waitqueue_head(&wq);
return 0; /*!!!!!!!!!!!!!!不要忘记!!!!!!!!!!!!!!!!!!!!!!!*/
ERR5:
for (--i; i >= minor; i--) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit mycdev_exit(void)
{
int i;
//注销设备节点
for (i = minor; i < minor + 3; i++) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
//字符设备驱动的注销
cdev_del(cdev);
//设备号释放
unregister_chrdev_region(MKDEV(major, minor), COUNT);
//释放cdev的内存空间
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
test.c
#include <head.h>
char buf[128] = { 0 };
int main(int argc, const char* argv[])
{
int fd1, fd2,ret;
fd_set rfds; //定义读表
if ((fd1 = open("/dev/mycdev0", O_RDWR)) ==-1)
PRINT_ERR("open mycdev0 error");
if ((fd2 = open("/dev/input/mouse0", O_RDWR)) ==-1)
PRINT_ERR("open mouse0 error");
while(1){
FD_ZERO(&rfds);
FD_SET(fd1,&rfds);
FD_SET(fd2,&rfds);
ret = select(fd2+1,&rfds,NULL,NULL,NULL);
if(ret == -1){
PRINT_ERR("select error");
}
if(FD_ISSET(fd1,&rfds)){
memset(buf,0,sizeof(buf));
read(fd1,buf,sizeof(buf));
printf("mycdev0:buf = %s\n",buf);
}
if(FD_ISSET(fd2,&rfds)){
memset(buf,0,sizeof(buf));
read(fd2,buf,sizeof(buf));
printf("mouse0:buf = %s\n",buf);
}
}
close(fd1);
close(fd2);
return 0;
}
4.4IO多路复用的内部原理
4.4.1VFS层的引出
__poll_t mycdev_poll(struct file* file,
struct poll_table_struct* wait)
{
// 1.定义返回值的变量
__poll_t mask = 0;
// 2.程序不能在这里阻塞,如果在这里阻塞,上图的流程就说不通了
//这个函数调用的时候会构造等待队列。阻塞过程发生在VFS(虚拟文件系统层)
poll_wait(file, &wq, wait);
// 3.如果数据准备好
if (condition) {
mask |= EPOLLIN; // EPOLLIN 可读 EPOLLOUT 可写
}
// 4.返回结果
return mask;
}
4.4.2IO多路复用sys_select分析
//vi -t sys_select
SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,
fd_set __user *, exp, struct __kernel_old_timeval __user *, tvp)
|
//SYSCALL_DEFINE5它是一个可以替换为sys_select函数
|
long sys_select(int n, fd_set __user * inp, fd_set __user * outp,
fd_set __user * exp, struct __kernel_old_timeval __user * tvp)
{
//n:最大的文件描述符的值加1
//inp:用户读表的首地址
//outp:写表的首地址
NULL
//exp:其他表的首地址
NULL
//tvp:超时时间 NULL
return kern_select(n, inp, outp, exp, tvp);
}
static int kern_select(int n, fd_set __user *inp, fd_set __user *outp,
fd_set __user *exp, struct __kernel_old_timeval __user *tvp)
{
int ret;
ret = core_sys_select(n, inp, outp, exp, to);
return poll_select_finish(&end_time, tvp, PT_TIMEVAL, ret);
}
int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,
fd_set __user *exp, struct timespec64 *end_time)
{
fd_set_bits fds;
void *bits;
int ret, max_fds;
size_t size, alloc_size;
struct fdtable *fdt;
long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];
//1.对最大文件描述符的值校验及更正
if (n < 0) //判断用户传递的n是否合法,如果不合法就直接报错返回
goto out_nofds;
//从进程中获取最大文件描述符和用户传递的最大文件描述符比对
//如果用户传递的不对强制更正。
fdt = files_fdtable(current->files);
max_fds = fdt->max_fds;
if (n > max_fds)
n = max_fds;
//2.在内核空间分配6张表的内存,将用户空间的三张表
//拷贝到内核空间,并将准备好文件描述符的三张表先清空
//in out ex :保存用户传递过来的表
//res_in res_out res_ex:保存准备好文件描述符的表
alloc_size = 6 * size;
bits = kvmalloc(alloc_size, GFP_KERNEL);
fds.in = bits;
fds.out = bits + size;
fds.ex = bits + 2*size;
fds.res_in = bits + 3*size;
fds.res_out = bits + 4*size;
fds.res_ex = bits + 5*size;
//将用户的读,写,其他表拷贝到刚分配的内核空间的表中
//fds.in fds.out fds.ex
if ((ret = get_fd_set(n, inp, fds.in)) ||
(ret = get_fd_set(n, outp, fds.out)) ||
(ret = get_fd_set(n, exp, fds.ex)))
goto out;
//清空准备好的文件描述符的表
zero_fd_set(n, fds.res_in);
zero_fd_set(n, fds.res_out);
zero_fd_set(n, fds.res_ex);
//3.遍历文件描述符
ret = do_select(n, &fds, end_time);
//4.将准备好的文件描述符拷贝到用户空间
if (set_fd_set(n, inp, fds.res_in) ||
set_fd_set(n, outp, fds.res_out) ||
set_fd_set(n, exp, fds.res_ex))
ret = -EFAULT;
}
static int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
{
ktime_t expire, *to = NULL;
struct poll_wqueues table;
poll_table *wait;
int retval, i, timed_out = 0;
u64 slack = 0;
__poll_t busy_flag = net_busy_loop_on() ? POLL_BUSY_LOOP : 0;
unsigned long busy_start = 0;
//1.校验最大的文件描述符的值
retval = max_select_fd(n, fds);
if (retval < 0)
return retval;
n = retval;
//2.填充一个初始化等待队列的函数,但是这个函数并没有在这里被调用
poll_initwait(&table);
wait = &table.pt; //将table中初始化好的函数指针赋值给wait结构体
//wait后面会传递给驱动的poll函数
retval = 0;
for (;;) {
unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
//定义六个指针,分别进行赋值
inp = fds->in; outp = fds->out; exp = fds->ex;
rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
unsigned long in, out, ex, all_bits, bit = 1, j;
unsigned long res_in = 0, res_out = 0, res_ex = 0;
__poll_t mask;
//取出来文件描述符表中第一个unsigned long的成员
//然后让inp指向下一个unsigned long
in = *inp++; out = *outp++; ex = *exp++;
all_bits = in | out | ex;
//如果取到的值为0,表示这里没有要监听的文件描述符
//执行下一循环取出下一个unsigned long的值,在判断
if (all_bits == 0) {
i += BITS_PER_LONG;
continue;
}
//循环判断那个in中的那个bit位是1
for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1) {
struct fd f;
if (i >= n) //如果i>=n说明已经到了最大文件描述符了直接退出
break;
//判断all_bits对应的bit为是否为1,如果不为1
//执行下一次循环,判断下一个bit是否为1.
if (!(bit & all_bits))
continue;
//这里的i是文件描述符,根据文件描述符可以取到file结构体
file = fd->fd_array[fd]->file
//f = fdget(i);
if (file) {
//调用驱动的poll函数执行,驱动的poll的
//返回值EPOLLIN或0,如果是EPOLLIN表示数据准备好了
//如果返回的是0,表示数据没有准备好
mask = file->f_op->poll(file, wait);
if ((mask & POLLIN_SET) && (in & bit)) {
res_in |= bit;
retval++;
wait->_qproc = NULL;
}
if ((mask & POLLOUT_SET) && (out & bit)) {
res_out |= bit;
retval++;
wait->_qproc = NULL;
}
if ((mask & POLLEX_SET) && (ex & bit)) {
res_ex |= bit;
retval++;
wait->_qproc = NULL;
}
}
}
}
//select什么时候返回?
//数据准备好返回
//超时时间到了返回
//信号到来的时候返回
if (retval || timed_out || signal_pending(current))
break;
//如果遍历所有的文件描述符之后,如果所有的驱动返回的值都是0
//表示select监听的所有文件描述符的数据都没有准备好,此时进程
//调用poll_schedule_timeout休眠
if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
to, slack))
timed_out = 1;
}
poll_freewait(&table);
return retval;
}
4.4.3IO多路复用总结
应用层:
select(maxfd+1,&rfds,NULL,NULL,NULL)
_____________________(系统调用swi 142)________________________________________
内核层: |vfs: NATIVE(142, sys_select)
|sys_select
|1.校验最大的文件描述符的值
|2.在内核空间分配6张表的内核,将用户空间的表拷贝到内核空间
|3.遍历文件描述符
| mask = fd_set->fd->fd_array[fd]->file->f_op->poll (file,wait);
|4.如果所有的文件描述符返回的mask都是0,此时进程休眠
|5.如果休眠的进程被唤醒了,再次遍历文件描述符表,从中找到mask不为0的
|文件描述符,将这些文件描述符放到准备好的文件描述符表中
|6.将准备好的文件描述符拷贝到用户空间
|__________________________________________________________________
|drivers
|1.定义返回值的变量 __poll_t mask=0;
|2.调用poll_wait函数
|3.如果数据准备好设置mask|=EPOLLIN
|4.return mask
______________________________________________________________________________
4.5select/poll/epoll的区别?
select: (表)
1.select最多能够监听的文件描述符的个数是1024
2.select需要反复构造表,需要反复从用户空间向内核空间拷贝要监听的表,效率低
3.如果select监听的所有的文件描述符没有准备好数据,进程休眠。如果有文件描述符
的数据准备好了,需要重新遍历一遍文件描述符的表,效率比较低。
poll: (结构体数组)
1.poll监听的文件描述符没有个数限制
2.poll不需要返回拷贝监听的文件描述符
3.如果poll监听的所有的文件描述符没有准备好数据,进程休眠。如果有文件描述符
的数据准备好了,需要重新遍历一遍文件描述符的表,效率比较低。
epoll: (红黑树)
1.epoll监听的文件描述符没有个数限制
2.epoll不需要返回拷贝监听的文件描述符
3.epoll当文件描述符数据准备好的时候,不需要遍历,可以直接拿到准备好的文件描述符。
4.6epoll的使用
4.6.1epoll的API
#include <sys/epoll.h>
int epoll_create(int size);
功能:创建epoll实例
参数:
@size:填写大于0的值即可
返回值:成功返回epfd,失败返回-1置位错误码
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:epoll的控制函数
参数:
@epfd:epoll的文件描述符
@op:控制方式
EPOLL_CTL_ADD:添加
EPOLL_CTL_MOD:修改
EPOLL_CTL_DEL:删除
@fd:被操作的文件描述符
@event:事件结构体
typedef union epoll_data {
void *ptr;
int fd; <=====一般只使用这个成员
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; //EPOLLIN 或EPOLLOUT
epoll_data_t data; //存放用户数据
};
返回值:成功返回0,失败返回-1置位错误码
int epoll_wait(int epfd, struct epoll_event *revents,
int maxevents, int timeout);
功能:阻塞等待准备好的文件描述符
参数:
@epfd:epoll的文件描述符
@revents:返回准备好的事件结构体
@maxevents:同时返回的最大的时间结构体的个数
@timeout:超时
>0 :毫秒超时
=0 :立即返回
=-1:不关心超时
返回值:成功大于0,表示准备好的文件描述符的个数
返回0,表示超时时间到了
失败返回-1置位错误码
4.6.2epoll的实例
#include <head.h>
int main(int argc, const char* argv[])
{
int epfd, fd,ret;
char buf[128] = {0};
struct epoll_event event;
struct epoll_event revents[10];
// 1.创建epoll
if ((epfd = epoll_create(10)) == -1)
PRINT_ERR("epoll create error");
// 2.将监听的文件描述符放入epoll
for (int i = 1; i < argc; i++) {
if ((fd = open(argv[i], O_RDWR)) == -1)
PRINT_ERR("open error");
event.events = EPOLLIN;
event.data.fd = fd;
if(epoll_ctl(epfd, EPOLL_CTL_ADD,fd,&event))
PRINT_ERR("epoll ctl error");
}
// 3.等待准备好的文件描述符
while(1){
if((ret = epoll_wait(epfd,revents,10,-1))==-1)
PRINT_ERR("epoll wait error");
for(int i=0;i<ret;i++){
if(revents[i].events & EPOLLIN){
memset(buf,0,sizeof(buf));
read(revents[i].data.fd,buf,sizeof(buf));
printf("fd=%d,data = %s\n",revents[i].data.fd,buf);
}
}
}
return 0;
}
5.IO模模型之异步通知
5.1异步通知的IO模型原理
在进程中注册一个信号处理函数,如果硬件的数据准备好的时候,会产生中断,
在中断处理函数中给这个进程发送信号即可。如果内核没有发出信号应用程序,
不需要阻塞,运行自己特有的代码即可。
应用层:
1.注册信号处理函数
signal(SIGIO,信号处理函数);
2.调用底层的fasync,在它内部完成发信号前的初始化
unsigned int flags = fcntl(fd,F_GETFL); //file->f_flags
fcntl(fd,F_SETFL,flags|FASYNC);
3.告诉内核进程的pid
fcntl(fd,F_SETOWN,getpid());
---------------------------------------------------------------------
vi -t sys_fcntl
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg)
err = do_fcntl(fd, cmd, arg, f.file);
switch (cmd) {
case F_GETFL:
err = filp->f_flags;
break;
case F_SETFL:
err = setfl(fd, filp, arg);
//arg=filp->f_flags|FASYNC
if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) {
error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);
----------------------------------|-----------------------------------
内核层:fops:
int (*fasync) (int fd, struct file *file, int on)
{
//在它内部完成发信号前的初始化
//int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
return fasync_helper(fd,file,on,&fapp);
}
发信号:
kill_fasync(fapp,SIGIO, POLL_IN/EPOLLIN);
5.2异步通知的实例
mycdev.c
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#define CNAME "mycdev"
#define COUNT 3
struct cdev* cdev;
int major = 238;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
struct fasync_struct *fapp;
int mycdev_open(struct inode* inode, struct file* filp)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t mycdev_read(struct file* filp,
char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
//将数据拷贝到用户空间
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_to_user(ubuf, kbuf, size);
if (ret) {
printk("copy data to user error\n");
return -EINVAL;
}
return size;
}
ssize_t mycdev_write(struct file* filp,
const char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if (ret) {
printk("copy data from user error\n");
return -EINVAL;
}
//发信号
kill_fasync(&fapp,SIGIO,EPOLLIN);
return size;
}
int mycdev_fasync(int fd, struct file *filp, int on)
{
return fasync_helper(fd,filp,on,&fapp);
}
int mycdev_close(struct inode* inode, struct file* filp)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
const 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, i;
dev_t devno;
// 1.分配对象
cdev = cdev_alloc();
if (cdev == NULL) {
printk("alloc cdev memory error\n");
ret = -ENOMEM;
goto ERR1;
}
// 2.对象初始化
cdev_init(cdev, &fops);
// 3.设备号的申请
if (major == 0) {
//动态申请
ret = alloc_chrdev_region(&devno, minor, COUNT, CNAME);
if (ret) {
printk("dynamic:request device number error\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
} else {
//静态指定
ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
if (ret) {
printk("static:request device number error\n");
goto ERR2;
}
}
// 4.字符设备驱动的注册
ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
if (ret) {
printk("register chardev error\n");
goto ERR3;
}
// 5.自动创建设备节点
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls)) {
printk("class create error\n");
ret = PTR_ERR(cls);
goto ERR4;
}
for (i = minor; i < minor + 3; i++) {
dev = device_create(cls, NULL, MKDEV(major, i), NULL, "mycdev%d", i);
if (IS_ERR(dev)) {
printk("device create error\n");
ret = PTR_ERR(dev);
goto ERR5;
}
}
return 0; /*!!!!!!!!!!!!!!不要忘记!!!!!!!!!!!!!!!!!!!!!!!*/
ERR5:
for (--i; i >= minor; i--) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit mycdev_exit(void)
{
int i;
//注销设备节点
for (i = minor; i < minor + 3; i++) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
//字符设备驱动的注销
cdev_del(cdev);
//设备号释放
unregister_chrdev_region(MKDEV(major, minor), COUNT);
//释放cdev的内存空间
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
test.c
#include <head.h>
int fd;
char buf[128] = { 0 };
void signal_handle(int signo)
{
if(signo == SIGIO){
memset(buf,0,sizeof(buf));
read(fd,buf,sizeof(buf));
printf("buf = %s\n",buf);
}
}
int main(int argc, const char* argv[])
{
if ((fd = open("/dev/mycdev0", O_RDWR)) == -1)
PRINT_ERR("open mycdev0 error");
// 1.注册信号处理函数
if ((signal(SIGIO, signal_handle)) == SIG_ERR)
PRINT_ERR("signal error");
// 2.调用底层的fasync
unsigned int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
// 3.告诉内核当前进程的pid
fcntl(fd, F_SETOWN, getpid());
while (1) {
sleep(1);
}
close(fd);
return 0;
}
DEV(major, i));
}
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit mycdev_exit(void)
{
int i;
//注销设备节点
for (i = minor; i < minor + 3; i++) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
//字符设备驱动的注销
cdev_del(cdev);
//设备号释放
unregister_chrdev_region(MKDEV(major, minor), COUNT);
//释放cdev的内存空间
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE(“GPL”);
test.c
```c
#include <head.h>
int fd;
char buf[128] = { 0 };
void signal_handle(int signo)
{
if(signo == SIGIO){
memset(buf,0,sizeof(buf));
read(fd,buf,sizeof(buf));
printf("buf = %s\n",buf);
}
}
int main(int argc, const char* argv[])
{
if ((fd = open("/dev/mycdev0", O_RDWR)) == -1)
PRINT_ERR("open mycdev0 error");
// 1.注册信号处理函数
if ((signal(SIGIO, signal_handle)) == SIG_ERR)
PRINT_ERR("signal error");
// 2.调用底层的fasync
unsigned int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
// 3.告诉内核当前进程的pid
fcntl(fd, F_SETOWN, getpid());
while (1) {
sleep(1);
}
close(fd);
return 0;
}