一个设备除了读写操作来收发数据或返回保存还有其他的操作。内核将读写之外的I/O操作都给了ioctl函数接口。文件I/O还具备多种模型,如阻塞、非阻塞、I/O口多路复用、异步I/O、异步通知等。
ioctl设备操作
iotcl是一个系统调用函数
int ioctl(int d, int request,...);
d:操作文件的文件描述符
request:不同操作的数值(命令)
第三个参数可选 ,是unsigned long类型,可以传递数值,还可以传递一个指针。
file_operations结构体和ioctl系统调用对应的驱动接口函数是unlocked_ioctl和compat_ioctl。
compat_ioctl:为了处理32位程序和64位内核兼容的一个函数接口,与体系结构相关
unlocked_ioctl:
long (* unlocked_ioctl) (struct file *,unsigned int,unsigned long);
第一个参数表示打开文件的file结构指针
第二个参数和系统调用的第二个参数request对应
第三个参数 对应系统调用函数的第三个参数
之前的ioctl接口调用之前要获得大内核锁(BLK,全局的粗粒度锁),如果ioctl的执行时间过长,会导致内核其他也需要大内核锁的代码要延迟很久,严重降低了效率,因此之后的内核废除了该接口(之前内核版本中,同ioctl系统调用的驱动接口也是ioctl)。
ioctl命令遵从的一种编码规则(内核源码版本中查看)
| 比特位 | 说明 |
| 31-30 | 00-命令不带参数 |
| 10-命令需要从驱动中获取数据,读方向 | |
| 01-命令需要把数据写入驱动,写方向 | |
| 11-命令既需要写入数据又需要获取数据,读写双向 | |
| 29-16 | 如果命令带参数,则指定参数所占的内存空间大小 |
| 15-8 | 每个驱动全局唯一的幻数(魔数) |
| 7-0 | 命令码 |
内核提供了一组宏来定义、提取命令中的字段信息
#define _IOC(dir,type,nr,size)\((dir) << _IOC_DIRSHIFT) | \((type) << _IOC_TYPESHIFT | \((nr) << _IOC_NRSHIFT | \((size) << _IOC_SIZESHIFT))#ifndef __KERNRL__
#define _IOC_TYPECHECK(t) (SIZEOF(T))
#endif#define _IO(type,nr) IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))#define _IOC_DIR(nr) ((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) ((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) ((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) ((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
定义命令使用的是最底层的宏_IOC,它将4个部分通过移位合并在一起。
部分代码示例:
.h
#ifndef _VSER_H
#define _VSER_Hstruct option {unsigned int datab;unsigned int parity;unsigned int stopb;
};#define VS_MAGIC 's'#define VS_SET_BAUD _IOW(VS_MAGIC, 0, unsigned int)
#define VS_GET_BAUD _IOW(VS_MAGIC, 1, unsigned int)
#define VS_SET_FFMT _IOW(VS_MAGIC, 2, struct option)
#define VS_GET_FFMT _IOW(VS_MAGIC, 3, struct option)#endif
.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>#include <linux/ioctl.h>
#include <linux/uaccess.h>#include "vser.h"#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"struct vser_dev {unsigned int baud;struct option opt;struct cdev cdev;
};DEFINE_KFIFO(vsfifo, char, 32);
static struct vser_dev vsdev;
...
static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{if (_IOC_TYPE(cmd) != VS_MAGIC)return -ENOTTY;switch (cmd) {case VS_SET_BAUD:vsdev.baud = arg;break;case VS_GET_BAUD:arg = vsdev.baud;break;case VS_SET_FFMT:if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))return -EFAULT;break;case VS_GET_FFMT:if (copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option)))return -EFAULT;break;default:return -ENOTTY;}return 0;
}static struct file_operations vser_ops = {.owner = THIS_MODULE,.open = vser_open,.release = vser_release,.read = vser_read,.write = vser_write,.unlocked_ioctl = vser_ioctl,
};static int __init vser_init(void)
{int ret;dev_t dev;dev = MKDEV(VSER_MAJOR, VSER_MINOR);ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);if (ret)goto reg_err;cdev_init(&vsdev.cdev, &vser_ops);vsdev.cdev.owner = THIS_MODULE;vsdev.baud = 115200;vsdev.opt.datab = 8;vsdev.opt.parity = 0;vsdev.opt.stopb = 1;ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);if (ret)goto add_err;return 0;add_err:unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:return ret;
}
...
先定义了一个结构类型struct option,包括波特率、奇偶效验、停止位,然后定义了设置波特率、获得波特率、设置帧格式、获取帧格式,共4个函数。
vser_ioctl函数,首先通过_IOC_TYPE宏提取出命令中的幻数字段,然后和预设的幻数比较,如果不匹配,返回-ENOTTY(表示参数不对);匹配,执行相应的操作。
在帧格式的设置和获取上使用了两个函数
unsigned long __must_check copy_to_user(void __user *to,const void *from,unsigned long n);
unsigned long __must_check copy_from_user(void *to,const void __user *from,unsigned long n);
__must_check要求必须检查函数返回值,to是目的内存地址,from是原内存地址,n是期望复制的字节数
这两个函数都返回没有复制成功的字节数,全部成功返回0.没有用memcpy函数是因为这两个函数用了access_ok来验证用户空间是否真实可读写,避免了在内核中的缺页故障了带来的问题。这两个函数还可能使进程休眠。如果只是复制简单的数据类型(char、int、short等),可以使用get_user和put_user宏。
get_user(x,p)
put_user(x,p)int ret = 0x123456;
int val;
put_user(ret,(int __user *)arg);
get_user(val,(int __user *)arg);
测试
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>#include "vser.h"int main(int argc, char *argv[])
{int fd;int ret;unsigned int baud;struct option opt = {8,1,1};fd = open("/dev/vser0", O_RDWR);if (fd == -1)goto fail;baud = 9600;ret = ioctl(fd, VS_SET_BAUD, baud);if (ret == -1)goto fail;ret = ioctl(fd, VS_GET_BAUD, baud);if (ret == -1)goto fail;ret = ioctl(fd, VS_SET_FFMT, &opt);if (ret == -1)goto fail;ret = ioctl(fd, VS_GET_FFMT, &opt);if (ret == -1)goto fail;printf("baud rate: %d\n", baud);printf("frame format: %d%c%d\n", opt.datab, opt.parity == 0 ? 'N' : \opt.parity == 1 ? 'O' : 'E', \opt.stopb);close(fd);exit(EXIT_SUCCESS);fail:perror("ioctl test");exit(EXIT_FAILURE);
}
proc文件操作
proc文件系统是一种伪文件系统,不存在于磁盘上,只存在于内存中,只有内核运行时才会动态生成里面的内容。这个文件系统通常挂在在/proc目录下,是内核开发者向用户到处信息的常用方式。
随着proc文件系统的内容增多,不推荐再使用这个文件系统来对驱动进行调试。对硬件来讲,取代的是sysfs文件系统。但某些时候,驱动开发这是会只是这个接口,比如只查看当前串口的波特率信息帧格式。
示例:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>#include "vser.h"#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"struct vser_dev {unsigned int baud;struct option opt;struct cdev cdev;struct proc_dir_entry *pdir;struct proc_dir_entry *pdat;
};DEFINE_KFIFO(vsfifo, char, 32);
static struct vser_dev vsdev;static int vser_open(struct inode *inode, struct file *filp)
{return 0;
}static int vser_release(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{int ret;unsigned int copied = 0;ret = kfifo_to_user(&vsfifo, buf, count, &copied);return ret == 0 ? copied : ret;
}static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{int ret;unsigned int copied = 0;ret = kfifo_from_user(&vsfifo, buf, count, &copied);return ret == 0 ? copied : ret;
}static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{if (_IOC_TYPE(cmd) != VS_MAGIC)return -ENOTTY;switch (cmd) {case VS_SET_BAUD:vsdev.baud = arg;break;case VS_GET_BAUD:arg = vsdev.baud;break;case VS_SET_FFMT:if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))return -EFAULT;break;case VS_GET_FFMT:if (copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option)))return -EFAULT;break;default:return -ENOTTY;}return 0;
}static struct file_operations vser_ops = {.owner = THIS_MODULE,.open = vser_open,.release = vser_release,.read = vser_read,.write = vser_write,.unlocked_ioctl = vser_ioctl,
};static int dat_show(struct seq_file *m, void *v)
{struct vser_dev *dev = m->private;seq_printf(m, "baudrate: %d\n", dev->baud);return seq_printf(m, "frame format: %d%c%d\n", dev->opt.datab, \dev->opt.parity == 0 ? 'N' : dev->opt.parity == 1 ? 'O': 'E', \dev->opt.stopb);
}static int proc_open(struct inode *inode, struct file *file)
{return single_open(file, dat_show, PDE_DATA(inode));
}static struct file_operations proc_ops = {.owner = THIS_MODULE,.open = proc_open,.release = single_release,.read = seq_read,.llseek = seq_lseek,
};static int __init vser_init(void)
{int ret;dev_t dev;dev = MKDEV(VSER_MAJOR, VSER_MINOR);ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);if (ret)goto reg_err;cdev_init(&vsdev.cdev, &vser_ops);vsdev.cdev.owner = THIS_MODULE;vsdev.baud = 115200;vsdev.opt.datab = 8;vsdev.opt.parity = 0;vsdev.opt.stopb = 1;ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);if (ret)goto add_err;vsdev.pdir = proc_mkdir("vser", NULL);if (!vsdev.pdir)goto dir_err;vsdev.pdat = proc_create_data("info", 0, vsdev.pdir, &proc_ops, &vsdev);if (!vsdev.pdat)goto dat_err;return 0;dat_err:remove_proc_entry("vser", NULL);
dir_err:cdev_del(&vsdev.cdev);
add_err:unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:return ret;
}static void __exit vser_exit(void)
{dev_t dev;dev = MKDEV(VSER_MAJOR, VSER_MINOR);remove_proc_entry("info", vsdev.pdir);remove_proc_entry("vser", NULL);cdev_del(&vsdev.cdev);unregister_chrdev_region(dev, VSER_DEV_CNT);
}module_init(vser_init);
module_exit(vser_exit);MODULE_LICENSE("GPL");
#ifndef _VSER_H
#define _VSER_Hstruct option {unsigned int datab;unsigned int parity;unsigned int stopb;
};#define VS_MAGIC 's'#define VS_SET_BAUD _IOW(VS_MAGIC, 0, unsigned int)
#define VS_GET_BAUD _IOW(VS_MAGIC, 1, unsigned int)
#define VS_SET_FFMT _IOW(VS_MAGIC, 2, struct option)
#define VS_GET_FFMT _IOW(VS_MAGIC, 3, struct option)#endif
在vser_dev结构中添加了两个struct proc_dir_entry类型的指针成员。
首先在/proc目录下建立一个vser目录,再在vser目录下建立一个info文件,两个指针分对应指向创建的目录和文件的目录项
proc_mkdir创建目录,第一个参数是目录名,第二个参数为NULL,是指在/proc目录下创建目录
proc_create_data创建文件,第一个参数是文件名,第二个参数是权限,第三个参数是目录的目录项指针,第四个参数是该文件的操作方法集合,第五个参数是该文件的私有数据。
proc_ops是该文件的操作方法集,其中release、read、llseek都是用内核已有的函数来实现,而open用自定义的方法proc_open来实现,proc_open又调用了single_open来辅助实现,并制定了对文件读操作更具体的实现。dat_show是这部分代码的关键。先从私有数据中获得设备结构地址(proc_create_dat函数调用时传递),然后使用seq_printf动态地产生被读取的内容。
非阻塞型I/O
设备不一定随时都能给用户提供服务,资源就有了可用和不可用两种状态。对虚拟串口设备来说,当用户想要读取串口中的数据时,如果FIFO中没有数据,那么设备对读进程就是资源不可用;但对写进程是资源可用的。当资源不可用时,应用程序和驱动一个的各种配合就组成多种I/O模型。
以非阻塞的方式打开设备,当资源不可用时,驱动应该立即返回,并用一个错误发EAGAIN来通知应用程序此时资源不可用,应用程序应该稍后再试。
部分代码示例:
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{int ret;unsigned int copied = 0;if (kfifo_is_empty(&vsfifo))if (filp->f_flags & O_NONBLOCK)return -EAGAIN;ret = kfifo_to_user(&vsfifo, buf, count, &copied);return ret == 0 ? copied : ret;
}static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{int ret;unsigned int copied = 0;if (kfifo_is_full(&vsfifo))if (filp->f_flags & O_NONBLOCK)return -EAGAIN;ret = kfifo_from_user(&vsfifo, buf, count, &copied);return ret == 0 ? copied : ret;
}
用kfifo_is_empty判断FIFO是否为空
用kfifo_is_full判断FIFO是否为满
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>#include "vser.h"int main(int argc, char *argv[])
{int fd;int ret;unsigned int baud;struct option opt = {8,1,1};char rbuf[32] = {0};char wbuf[32] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";fd = open("/dev/vser0", O_RDWR | O_NONBLOCK);if (fd == -1)goto fail;ret = read(fd, rbuf, sizeof(rbuf));if (ret < 0)perror("read");ret = write(fd, wbuf, sizeof(wbuf));if (ret < 0)perror("first write");ret = write(fd, wbuf, sizeof(wbuf));if (ret < 0)perror("second write");close(fd);exit(EXIT_SUCCESS);fail:perror("ioctl test");exit(EXIT_FAILURE);
}
用O_NONBLOCK标志来表示以非阻塞的方式打开设备文件。首先开始读虚拟串口,此时FIFO没有数据,立即返回,报告错误“Resourse temporarily unavailable”,接着程序向虚拟串口写了32个字节的数据将FIFO填满,最后发起一次写操作,因为没有空间容纳数据,所以第二次写操作返回错误资源不可用。
阻塞型I/O(默认方式)
当进程一阻塞的方式打开设备文件时,如果资源不可用,那么进程阻塞(休眠)。
如果进程发现资源不可用,主动将自己的状态设置为TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE,然后将自己加入一个驱动所维护的等待队列中,最后调用schedule主动放弃CPU,操作系统将之从运行队列上移除,并调度其他的进程执行。
相比非阻塞I/O,最大的优点是资源不可用时,不占用CPU时间,提高系统效率,而非阻塞I/O时定期尝试。但阻塞型I/O有一个大缺点,就是进程在休眠期间不能做其他的事情。
休眠也需要对应的唤醒操作(中断处理程序、信号唤醒,超时自动唤醒等)。
要实现阻塞操作,最重要的数据结构就是等待队列。
等待队列有很多宏和函数,常见的一部分:
DECLARC_WAIT_QUEUE_HEAD(name)
init_waitqueue_head(q)wait_event(wq,condition)
wait_event_timeout(wq,condition,timeout)
wake_up(x)wait_event_interruptible(wq,condition)
wait_event_interruptible_timeout(wq,condition,timeout)
wait_event_interruptible_exclusive(wq,condition)
wake_up_interruptible(x)
wake_up(x)wait_event_interruptible_locked(wq,condition)
wait_event_interuptible_locked_irq(wq,condiition)
wait_event_interruptible_exclusive_locked(wq,condition)
wait_event_interruptible_exclusive_locked_irq(wq,condition)
wake_up_locked(x)
DECLARC_WAIT_QUEUE_HEAD静态定义了一个等待队列头
init_waitqueue_head用于初始化一个等待队列头
wait_event是在条件condition不成立的情况下将当前进程放入到等待队列并休眠的基本操作
timeout超时限制
interruptible表示进程在休眠时可以通过信号唤醒
exclusive表示该进程有排他性,在默认情况下,唤醒操作将唤醒等待队列的所有进程,如果是一个排他的进程休眠,那么唤醒操作在唤醒这个进程后不会继续唤醒其他进程
locked要求在调用前要先获得等待队列内部的锁
irq要求在上锁的同时禁止中断。
如果这些函数不带timeout,那么返回0表示成功唤醒;返回-ERESTARSYS表示被信号唤醒;如果带timeout,返回0表示超时,返回大于0的值表示唤醒成功,这个值是离超时还剩余的时间。
唤醒:带locked用wake_up_locked唤醒;不带locked而带interruptible的,用wake_up_interruptible唤醒;其他的用wake_up唤醒(也可以用这个)。
部分代码:
struct vser_dev {unsigned int baud;struct option opt;struct cdev cdev;wait_queue_head_t rwqh;wait_queue_head_t wwqh;
};
...
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{int ret;unsigned int copied = 0;if (kfifo_is_empty(&vsfifo)) {if (filp->f_flags & O_NONBLOCK)return -EAGAIN;if (wait_event_interruptible(vsdev.rwqh, !kfifo_is_empty(&vsfifo)))return -ERESTARTSYS;}ret = kfifo_to_user(&vsfifo, buf, count, &copied);if (!kfifo_is_full(&vsfifo))wake_up_interruptible(&vsdev.wwqh);return ret == 0 ? copied : ret;
}static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{int ret;unsigned int copied = 0;if (kfifo_is_full(&vsfifo)) {if (filp->f_flags & O_NONBLOCK)return -EAGAIN;if (wait_event_interruptible(vsdev.wwqh, !kfifo_is_full(&vsfifo)))return -ERESTARTSYS;}ret = kfifo_from_user(&vsfifo, buf, count, &copied);if (!kfifo_is_empty(&vsfifo))wake_up_interruptible(&vsdev.rwqh);return ret == 0 ? copied : ret;
}
...
static int __init vser_init(void)
{int ret;dev_t dev;dev = MKDEV(VSER_MAJOR, VSER_MINOR);ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);if (ret)goto reg_err;cdev_init(&vsdev.cdev, &vser_ops);vsdev.cdev.owner = THIS_MODULE;vsdev.baud = 115200;vsdev.opt.datab = 8;vsdev.opt.parity = 0;vsdev.opt.stopb = 1;ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);if (ret)goto add_err;init_waitqueue_head(&vsdev.rwqh);init_waitqueue_head(&vsdev.wwqh);return 0;add_err:unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:return ret;
}
测试可以用cat读取设备,echo写入,ctrl+c结束。ps命令查看进程。
I/O多路复用
在应用层,由于历史原因,I/O多路复用由三种方式,分别是poll,select以及Linux特有的epoll。
poll:
int poll(struct pollfd *fds,nfds_t nfds,int timeout);struct pollfd{int fd;short events;short revents;
};
fds:要监听的文件描述符集合,类型为struct pollfd的指针
nfds:监听的文件描述符的个数
timeout:超时值(毫秒),负数表示一直监听,一直到被监听的文件描述符集合中的任意一个设备发生了事件才会返回。
struct pollfd成员:
fd:要监听的文件描述符
events:监听的事件
revents:返回的事件
常见的事件有POLLIN、POLLOUT分别表示设备可以无阻塞地读、写。
POLLRDNORM和POLLWRNORM是在_XOPEN_SOURCE宏被定义时所引入的事件,POLLRDNORM通常和POLLIN等价,POLLWRNORM和POLLOUT等价。
示例:
一个既要监听键盘又要监听串口的程序,当用户按下键盘的按键后,将键值转换成字符串后通过串口发送出去,当串口接收到数据后,在屏幕上显示。
部分代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <linux/input.h>#include "vser.h"int main(int argc, char *argv[])
{int ret;struct pollfd fds[2];char rbuf[32];char wbuf[32];struct input_event key;fds[0].fd = open("/dev/vser0", O_RDWR | O_NONBLOCK);if (fds[0].fd == -1) goto fail;fds[0].events = POLLIN;fds[0].revents = 0;fds[1].fd = open("/dev/input/event1", O_RDWR | O_NONBLOCK);if (fds[1].fd == -1) goto fail;fds[1].events = POLLIN;fds[1].revents = 0;while (1) {ret = poll(fds, 2, -1);if (ret == -1)goto fail;if (fds[0].revents & POLLIN) {ret = read(fds[0].fd, rbuf, sizeof(rbuf));if (ret < 0)goto fail;puts(rbuf);}if (fds[1].revents & POLLIN) {ret = read(fds[1].fd, &key, sizeof(key));if (ret < 0)goto fail;if (key.type == EV_KEY) {sprintf(wbuf, "%#x\n", key.code);ret = write(fds[0].fd, wbuf, strlen(wbuf) + 1);if (ret < 0)goto fail;}}}fail:perror("poll test");exit(EXIT_FAILURE);
}
以非阻塞方式打开两个设备文件,初始化。调用poll监听,如果被监听的设备没有一个设备文件可读,那么poll就会一直阻塞,直到键盘或串口任意一个设备能够读取数据才返回;poll返回值,如果返回值不为负值,至少有一个设备可读。如果从串口读到数据则在标准输出上打印,如果键盘上读到数据,判断按键的类型,若为EV_KEY则将键值转换为字符串,通过串口发送。虚拟串口内循环。
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>#include <linux/ioctl.h>
#include <linux/uaccess.h>#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>#include "vser.h"struct vser_dev {unsigned int baud;struct option opt;struct cdev cdev;wait_queue_head_t rwqh;wait_queue_head_t wwqh;
};static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{int ret;unsigned int copied = 0;if (kfifo_is_empty(&vsfifo)) {if (filp->f_flags & O_NONBLOCK)return -EAGAIN;if (wait_event_interruptible_exclusive(vsdev.rwqh, !kfifo_is_empty(&vsfifo)))return -ERESTARTSYS;}ret = kfifo_to_user(&vsfifo, buf, count, &copied);if (!kfifo_is_full(&vsfifo))wake_up_interruptible(&vsdev.wwqh);return ret == 0 ? copied : ret;
}static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{int ret;unsigned int copied = 0;if (kfifo_is_full(&vsfifo)) {if (filp->f_flags & O_NONBLOCK)return -EAGAIN;if (wait_event_interruptible_exclusive(vsdev.wwqh, !kfifo_is_full(&vsfifo)))return -ERESTARTSYS;}ret = kfifo_from_user(&vsfifo, buf, count, &copied);if (!kfifo_is_empty(&vsfifo))wake_up_interruptible(&vsdev.rwqh);return ret == 0 ? copied : ret;
}unsigned int vser_poll(struct file *filp, struct poll_table_struct *p)
{int mask = 0;poll_wait(filp, &vsdev.rwqh, p);poll_wait(filp, &vsdev.wwqh, p);if (!kfifo_is_empty(&vsfifo))mask |= POLLIN | POLLRDNORM;if (!kfifo_is_full(&vsfifo))mask |= POLLOUT | POLLWRNORM;return mask;
}static struct file_operations vser_ops = {.owner = THIS_MODULE,.open = vser_open,.release = vser_release,.read = vser_read,.write = vser_write,.unlocked_ioctl = vser_ioctl,.poll = vser_poll,
};static int __init vser_init(void)
{int ret;dev_t dev;dev = MKDEV(VSER_MAJOR, VSER_MINOR);ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);if (ret)goto reg_err;cdev_init(&vsdev.cdev, &vser_ops);vsdev.cdev.owner = THIS_MODULE;vsdev.baud = 115200;vsdev.opt.datab = 8;vsdev.opt.parity = 0;vsdev.opt.stopb = 1;ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);if (ret)goto add_err;init_waitqueue_head(&vsdev.rwqh);init_waitqueue_head(&vsdev.wwqh);return 0;add_err:unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:return ret;
}
略