Linux系统中的IO模型
1.1 linux系统中的IO模型的种类
(1)非阻塞IO模型
(2)阻塞IO模型
(3)IO多路复用
(4)异步通知
1.2 非阻塞IO模型
非阻塞IO模型:当应用程序通过非阻塞的方式打开文件的时候,通过read读取
数据的时候,不管数据是否准备好,都要立即返回。
fd = open("/dev/mycdev0",O_RDWR|O_NONBLOCK); //使用非阻塞的方式打开文件
read(fd,buf,sizeof(buf));
--------------------------------------------------------
driver_read(struct file *file,,,)
{
if(file->f_flags &O_NONBLOCK){
//非阻塞,不管数据是否准备好都立即将数据拷贝到用户空间
}
}
1.3 阻塞IO模型
1.3.1 阻塞IO模型的实现流程
阻塞IO模型:应用层以阻塞的方式打开设备文件,然后调用read函数读取硬件的数据。
如果硬件的数据没有准备好,需要让进程进入到休眠的状态。当硬件的数据准备好的
时候产生中断,在中断的处理函数中唤醒休眠的进程即可。
1.3.2阻塞IO模型函数调用流程
fd = open("/dev/mycdev0",O_RDWR); //使用阻塞的方式打开文件
read(fd,buf,sizeof(buf));
--------------------------------------------------------
driver_read(struct file *file,,,)
{
if(file->f_flags &O_NONBLOCK){
//非阻塞
}else{
//阻塞,让进程进入休眠状态
}
}
中断的处理函数:
唤醒休眠的进程
1.3.3阻塞IO模型的API
1.定义等待队列头
wait_queue_head_t wq;
2.初始化等待队列头
init_waitqueue_head(&wq);
3.调用对应的函数完成阻塞(定义等待队列项,将等待队列项放到队列的队尾,进程休眠)
wait_event(wq, condition)
//让进程进入到不可中断的等待态
wait_event_interruptible(wq, condition)
//让进程进入到可中断的等待态
//返回值:如果是数据准备好了它返回0,如果是信号唤醒的进程的休眠返回-ERESTARTSYS
注:condition代表的是数据是否准备好的标志,如果condition为真代表数据准备好了
进程不需要休眠,如果condition为假代表数据没有准备好进程需要进入休眠的状态。
4.唤醒休眠的进程
condition=1;
wake_up(&wq);
wake_up_interruptible(&wq);
注:上述的两个函数唤醒的是等待队列,队列中的进程是否真的被唤醒取决于
condition的真假,如果condition为真,进程就准备被唤醒了,如果condition
为假进程没有被唤醒,进程继续休眠。

1.3.4阻塞IO模型的实例
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>
#define CNAME "mycdev"
struct cdev* cdev;
unsigned int major = 511;
unsigned int minor = 0;
const int count = 3;
struct class* cls;
struct device* dev;
char kbuf[128] = { 0 };
wait_queue_head_t wq; //定义等待队列头
int condition = 0; //数据是否准备好的状态,默认没有准备好
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 __user* ubuf, size_t size, loff_t* offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
//判断用户是否阻塞的方式打开的设备文件
if (file->f_flags & O_NONBLOCK) {
//非阻塞
return -EINVAL;
} else {
//阻塞
//如果是数据准备好了它返回0,如果是信号唤醒的进程的休眠返回-ERESTARTSYS
ret = wait_event_interruptible(wq, condition);
if (ret) {
//使用Ctrl+c结束进程时 会打印下面这句话
printk("receive signal.....\n");
return ret;
}
}
//将数据拷贝到用户空间
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_to_user(ubuf, kbuf, size);
if (ret) {
printk("copy data to user error\n");
return -EIO;
}
// 传递完数据 将condition清零 等待数据准备
condition=0;
return size;
}
ssize_t mycdev_write(struct file* file,
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 form user error\n");
return -EIO;
}
//当得到用户空间的数据 将condition置为1 代表数据准备好了 同时唤醒正在休眠的进程
//驱动的write可以唤醒驱动的read
condition = 1;
wake_up_interruptible(&wq);//唤醒的工作
return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
const struct file_operations fops = {
.open = mycdev_open,
.read = mycdev_read,
.write = mycdev_write,
.release = mycdev_close,
};
static int __init mycdev_init(void)
{
int ret, i;
dev_t devno;
// 1.分配对象
cdev = cdev_alloc();
if (cdev == NULL) {
printk("cdev alloc memory error\n");
ret = -ENOMEM;
goto ERR1;
}
// 2.初始化对象
cdev_init(cdev, &fops);
// 3.申请设备号
if (major > 0) {
//静态指定
ret = register_chrdev_region(MKDEV(major, minor), count, CNAME);
if (ret) {
printk("static:request device number error\n");
goto ERR2;
}
} else {
//动态申请
ret = alloc_chrdev_region(&devno, 0, count, CNAME);
if (ret) {
printk("dynamic:request device number error\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
// 4.注册
ret = cdev_add(cdev, MKDEV(major, minor), count);
if (ret) {
printk("register char device driver 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 = 0; i < count; 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 >= 0; 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 = 0; i < count; i++) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
cdev_del(cdev);
unregister_chrdev_region(MKDEV(major, minor), count);
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
test.c
#include <head.h>
char buf[128] = "i am test block IO mode....";
int main(int argc, const char* argv[])
{
int fd;
pid_t pid;
if ((fd = open("/dev/mycdev0", O_RDWR)) == -1)
PRINT_ERR("open error");
//使用父子进程验证 write唤醒read
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("buf = %s\n", buf);
}
} else {
while(1){
//保证子进程中的read先执行
sleep(3);
write(fd,buf,sizeof(buf));
}
wait(NULL);
}
close(fd);
return 0;
}
1.3.5 阻塞实现的流程
//让进程进入到可中断的等待态 (信号)
//wq_head:等待队列头
//condition:如果condition为真代表数据准备好,如果condition为假代表数据没有准备好
//返回0代表数据准备好,如果返回-ERESTARTSYS代表信号唤醒的休眠
#define wait_event_interruptible(wq_head, condition)
({
int __ret = 0;
//如果condition为假if语句成立,如果condition为真if不成立
if (!(condition))
__ret = __wait_event_interruptible(wq_head, condition);
__ret; //这个就是宏的返回值
})
#define __wait_event_interruptible(wq_head, condition)
___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0, schedule())
#define ___wait_event(wq_head, condition, state, exclusive, ret, cmd)
({
//1.定义等待队列项的结构体
struct wait_queue_entry __wq_entry;
long __ret = ret;//返回值的变量
//初始化等待队列项
/*
void init_wait_entry(struct wait_queue_entry *wq_entry, int flags)
{
wq_entry->flags = flags; //0
wq_entry->private = current; //进程的结构体 task_struct
wq_entry->func = autoremove_wake_function; //唤醒的函数
INIT_LIST_HEAD(&wq_entry->entry); //构成队列的链表
}
*/
init_wait_entry(&__wq_entry,0);
for (;;) {
//1.将等待队列项放到等待队列头之后
//2.将进程的状态标记为可中断的等待态(此时进程没有真正的休眠)
long __int = prepare_to_wait_event(&wq_head, &__wq_entry, TASK_INTERRUPTIBLE);
if (condition)
//数据是否准备好,如果准备好就退出循环
break;
if (signal_pending_state(state, current)) {
//判断是否收到了信号,如果收到信号退出循环
list_del_init(&wq_entry->entry);
__ret = -ERESTARTSYS;;
goto __out;
}
schedule();//让进程进入休眠状态的函数
//主动放弃cpu,进程真正进入到休眠状态
}
finish_wait(&wq_head, &__wq_entry);
//将等待队列项从等待队列上删除
//将进程的状态标记为运行态
__out: __ret;
})
1.4 IO多路复用的IO模型
1.4.1 IO多路复用的实现的流程
IO多路复用:在同一个进程中想同时监听多个硬件的数据,此时就需要使用IO多路复用
的机制。将需要监听的文件描述符放在需要监听文件描述符的表中,通过select/poll/epoll
完成监听,如果所有的硬件的数据都没有准备好此时进程休眠。当有硬件的数据准备好的时候
会产生硬件的中断,在中断的处理函数中唤醒休眠的进程,当进程被唤醒之后从准备好的文件
描述符表中找出准备好的文件描述符,从里面读取数据即可。
1.4.2 IO多路复用接口调用流程
user:
fd1 = open("/dev/mycdev0",O_RDWR);
fd2 = open("/dev/input/mouse0",O_RDWR);
fd_set rfds; //定义读表
FD_ZERO(&rfds); //清空表
FD_SET(fd1,&rfds);//将fd1放入表中
FD_SET(fd2,&rfds)//将fd2放入表中
select(maxfd+1,&rfds,NULL,NULL,NULL)
if(FD_ISSET(fd1,&rfds)){
//fd1数据准备好了
read(fd1,buf1,sizeof(buf1));
}
if(FD_ISSET(fd2,&rfds)){
//fd2数据准备好了
read(fd2,buf2,sizeof(buf2));
}
----------------------------------------------
kernel:fops: //应用层的select/poll/epoll对应的都是驱动中的poll函数
grep ".poll =" * -nR
__poll_t (*poll) (struct file *file, struct poll_table_struct *wait);
{
1.定义返回值的变量__poll_t mask = 0;
2.调用poll_wait(是和阻塞相关的函数)
poll_wait(file, 等待队列头, wait);
3.判断数据是否准备好了
if(condition){
mask |= EPOLLIN; //EPOLLIN 数据可读 EPOLLOUT数据可写
}
4.返回mask
return mask;
}
1.4.3 IO多路复用实例
test.c
#include <head.h>
#include <sys/select.h>
char buf[128] = "i am test block IO mode....";
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 error");
if ((fd2 = open("/dev/input/mouse0", O_RDWR)) == -1)
PRINT_ERR("open error");
while (1) {
FD_ZERO(&rfds); //清空表
FD_SET(fd1, &rfds); //将fd1放入表中
FD_SET(fd2, &rfds); //将fd2放入表中
ret = select(fd2 + 1, &rfds, NULL, NULL, NULL);
if (ret == -1)
PRINT_ERR("select error");
if (FD_ISSET(fd1, &rfds)) {
// fd1数据准备好了
memset(buf, 0, sizeof(buf));
read(fd1, buf, sizeof(buf));
printf("mycdev0:%s\n", buf);
}
if (FD_ISSET(fd2, &rfds)) {
// fd2数据准备好了
memset(buf, 0, sizeof(buf));
read(fd2, buf, sizeof(buf));
printf("mouse0:%s\n", buf);
}
}
close(fd1);
return 0;
}
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"
struct cdev* cdev;
unsigned int major = 511;
unsigned int minor = 0;
const int count = 3;
struct class* cls;
struct device* dev;
char kbuf[128] = { 0 };
wait_queue_head_t wq; //定义等待队列头
int condition = 0; //数据是否准备好的状态,默认没有准备好
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 __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 -EIO;
}
//将condition清零
condition=0;
return size;
}
ssize_t mycdev_write(struct file* file,
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 form user error\n");
return -EIO;
}
condition = 1;
wake_up_interruptible(&wq);//唤醒的工作
return size;
}
__poll_t mycdev_poll(struct file *file, struct poll_table_struct *wait)
{
//定义返回值的变量
__poll_t mask = 0;
// 调用poll_wait
poll_wait(file, &wq_head, wait);
// 判断数据是否准备好了
if (condition) {
mask |= EPOLLIN; // EPOLLIN 表示数据可读 EPOLLOUT代表数据可写
}
//返回mask
return mask;
}
int mycdev_close(struct inode* inode, struct file* file)
{
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("cdev alloc memory error\n");
ret = -ENOMEM;
goto ERR1;
}
// 2.初始化对象
cdev_init(cdev, &fops);
// 3.申请设备号
if (major > 0) {
//静态指定
ret = register_chrdev_region(MKDEV(major, minor), count, CNAME);
if (ret) {
printk("static:request device number error\n");
goto ERR2;
}
} else {
//动态申请
ret = alloc_chrdev_region(&devno, 0, count, CNAME);
if (ret) {
printk("dynamic:request device number error\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
// 4.注册
ret = cdev_add(cdev, MKDEV(major, minor), count);
if (ret) {
printk("register char device driver 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 = 0; i < count; 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 >= 0; 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 = 0; i < count; i++) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
cdev_del(cdev);
unregister_chrdev_region(MKDEV(major, minor), count);
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
1.5 sys_select执行流程总结
US:
select(maxfd+1,&rfds,NULL,NULL,NULL);
---------------------------------------------------------------
VFS(虚拟文件系统): sys_select
1.校验最大文件描述符
2.分配6张表的内存(前三张表用来存储用户的文件描述符,后三张表存储准备好的文件描述符)
3.将用户空间表拷贝到内存空间
4.遍历文件描述符
mask = fd_set->fd->fd_array[fd]->file->f_op->poll(file,wait);
如果所有的文件描述符对应驱动返回的mask都是0,说明所有的硬件的数据
都没有准备好
5.进程休眠 (schedule相关的函数)
6.如果进程被唤醒(数据准备好,超时时间到,收到了信号),再次遍历
文件描述符,找出数据准备好的文件描述符(如果mask不为0,说明数据就准备好了)
mask = fd_set->fd->fd_array[fd]->file->f_op->poll(file,wait);
将准备好的文件描述符放到准备好的文件描述符的表中
7.将准备好的文件描述符拷贝到用户空间
---------------------------------------------------------------
驱动层:
__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.返回mask
return mask;
}
1.6 select/poll/epoll有什么区别?
select:表
1.最多只能监听1024个文件描述
2.用户空间的表会被清空,需要反复构造文件描述符的表,需要反复从用户空间向内核空间拷贝表效率低
3.当select进程休眠被唤醒之后,需要再次编译文件描述符的表,找出准备好的文件描述符,效率比较低。
poll:结构体数组
1.poll监听的文件描述符没有个数限制
2.poll的表不会被清空,不需要反复拷贝文件描述符,效率比较高。
3.当poll进程休眠被唤醒之后,需要再次编译文件描述符的表,找出准备好的文件描述符,效率比较低。
epoll:红黑树
1.epoll监听的文件描述符没有个数限制
2.epoll的表不会被清空,不需要反复拷贝文件描述符,效率比较高。
3.当epoll进程休眠被唤醒之后,能直接拿到准备好的文件描述符,不需要遍历,效率高。
1.7 epoll的使用
#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 *events,int maxevents, int timeout);
功能:阻塞等待文件描述符就绪
参数:
@epfd:epoll的文件描述符
@events:准备好的事件的结构体地址
@maxevents:返回的最大的文件描述符的个数
@timeout:超时
>0 :毫秒级别的超时时间
=0 :立即返回
=-1:不关心超时时间
返回值:
成功返回准备好的文件描述符的个数
返回0代表超时时间到了
失败返回-1置位错误码
代码实现
#include <head.h>
#include <sys/epoll.h>
int main(int argc, const char* argv[])
{
int epfd, fd, ret;
struct epoll_event event;
struct epoll_event revents[10];
char buf[128] = {0};
if ((epfd = epoll_create(10)) == -1)
PRINT_ERR("epoll create error");
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");
}
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;
}
1.8 异步通知IO模型
1.8.1 异步通知IO模型实现思想
异步通知IO模型:应用程序在使用异步通知IO模型的时候需要使用signal(SIGIO,handler)
为SIGIO信号绑定一个信号处理函数。应用程序就可以执行任意自己想要执行的代码。当
硬件的数据准备号之后会产生中断,在中断的处理函数中给这个进程发送信号。当进程
收到信号后执行信号处理函数即可。(信号驱动IO)
异步修饰的是通知而不是IO模型
1.8.2 异步通知IO模型实现流程
us:
void handle(int signo)
{
//信号处理函数,在信号处理函数中将数据读走即可
}
//1.注册信号处理函数
signal(SIGIO,handle);
//2.调用驱动的fasync函数,完成异步通知队列的初始化
unsigned int flags=fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags | FASYNC);
//3.告诉驱动将信号发给当前的进程
fcntl(fd,F_SETOWN,getpid());
---------------------------------------------------------
VFS: 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);
}
break;
}
---------------------------------------------------------
ks:fops:
struct fasync_struct *fapp;
int (*fasync) (int fd, struct file *filp, int on)
{ //初始化异步通知队列
return fasync_helper(fd,filep,on,&fapp);
}
//发送信号
void kill_fasync(&fapp, SIGIO, POLL_IN);
1.8.3 异步通知IO模型的实例
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"
struct cdev* cdev;
unsigned int major = 511;
unsigned int minor = 0;
const int count = 3;
struct class* cls;
struct device* dev;
char kbuf[128] = { 0 };
struct fasync_struct *fapp;
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 __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 -EIO;
}
return size;
}
ssize_t mycdev_write(struct file* file,
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 form user error\n");
return -EIO;
}
//发送SIGIO信号
kill_fasync(&fapp,SIGIO,POLL_IN);
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* file)
{
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("cdev alloc memory error\n");
ret = -ENOMEM;
goto ERR1;
}
// 2.初始化对象
cdev_init(cdev, &fops);
// 3.申请设备号
if (major > 0) {
//静态指定
ret = register_chrdev_region(MKDEV(major, minor), count, CNAME);
if (ret) {
printk("static:request device number error\n");
goto ERR2;
}
} else {
//动态申请
ret = alloc_chrdev_region(&devno, 0, count, CNAME);
if (ret) {
printk("dynamic:request device number error\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
// 4.注册
ret = cdev_add(cdev, MKDEV(major, minor), count);
if (ret) {
printk("register char device driver 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 = 0; i < count; 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 >= 0; 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 = 0; i < count; i++) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
cdev_del(cdev);
unregister_chrdev_region(MKDEV(major, minor), count);
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
test.c
#include <head.h>
#include <signal.h>
int fd;
char buf[128] = { 0 };
void 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 error");
if ((SIG_ERR == signal(SIGIO, handle)))
PRINT_ERR("signal error");
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | FASYNC);
fcntl(fd, F_SETOWN, getpid());
while (1);
close(fd);
return 0;
}

399

被折叠的 条评论
为什么被折叠?



