目录
- 3 信号驱动的异步通知
- 3.1 linux异步通知编程
- 3.1.1 什么是信号
- 3.1.2 信号的工作流程:
- 3.2. 应用层
- 3.2.1 信号接收 signal函数
- 3.2.2 应用层 fcntl 函数
- 3.2.3 应用层信号驱动机制步骤
- 3.3 驱动层
- 3.3.1 驱动层模板
- 3.3.2 驱动层 实现fasync函数
- 3.3.3 fasync_helper
- 3.3.4 struct fasync_struct
- 3.3.5 kill_fasync函数
- 3.4 实例
接上篇,继续内核 I/O的五种模式的解读。
3 信号驱动的异步通知
异步通知的意思是:一旦设备就绪,则主动通知应用程序,非常类似于硬件上“中断”的概念,比较准确的的称谓是“信号驱动的异步I/O”。
因此,阻塞I/O意味着应用程序需要一直等待设备可访问后才能访问
非阻塞I/O中使用poll(),则需要循环地查询设备是否可访问,如能访问了再访问。
而异步通知则意味着设备会主动通知应用程序自身可访问了,之后用户再进行I/O处理。
三种方式本身无优劣,在于根据应用场景进行选择。
3.1 linux异步通知编程
3.1.1 什么是信号
- 信号是软中断,用于通知进程某个事件已经发生。
- 进程可以选择如何响应信号:忽略、默认处理、自定义处理等。
- 常见信号有:SIGINT(键盘中断)、SIGKILL(强制终止)、SIGSTOP(暂停进程)、SIGCONT(继续运行进程)等。
#include <singal.h>
信号值 | 信号名 | 说明 |
---|---|---|
1 | SIGHUP | 终端断线,用于进程与控制终端的连接 |
2 | SIGINT | 键盘中断,用于进程的输入终端 |
3 | SIGQUIT | 键盘的退出键(Ctrl+/) |
4 | SIGILL | 非法指令 |
5 | SIGTRAP | 跟踪陷阱 |
6 | SIGABRT | 由abort(3)发出的异常终止条件信号 |
7 | SIGBUS | 总线错误 |
8 | SIGFPE | 算术运算错误异常 |
9 | SIGKILL | 强制终止进程 |
10 | SIGUSR1 | 用户自定义信号1 |
11 | SIGSEGV | 无效内存引用 |
12 | SIGUSR2 | 用户自定义信号2 |
13 | SIGPIPE | 向一个没有读取端口的管道或套接字写入数据 |
14 | SIGALRM | 时钟定时信号 |
15 | SIGTERM | 终止进程的信号 |
16 | SIGSTKFLT | 协处理器堆栈错误 |
17 | SIGCHLD | 子进程停止或退出时向其父进程发送的信号 |
18 | SIGCONT | 如果进程已停止,则继续执行 |
19 | SIGSTOP | 停止进程运行(不能被捕获、阻塞或忽略) |
20 | SIGTSTP | 用户键入susp字符(通常是Ctrl+Z) |
21 | SIGTTIN | 后台进程企图从控制终端读入 |
22 | SIGTTOU | 后台进程企图写出到控制终端 |
23 | SIGURG | 由套接字上的紧急数据到达产生 |
24 | SIGXCPU | 超过CPU时间限制 |
25 | SIGXFSZ | 超过文件大小限制 |
26 | SIGVTALRM | 虚拟时钟定时信号 |
27 | SIGPROF | 定时器信号 |
28 | SIGWINCH | 窗口大小更改 |
29 | SIGIO | I/O准备就绪 |
30 | SIGPWR | 电源错误 |
31 | SIGSYS | 错误的参数或错误的 syscall 发生 |
3.1.2 信号的工作流程:
- 产生信号:通过 kill 命令向进程发送信号,或按 Ctrl+C 键盘中断。
- 信号递送:内核向目标进程递送信号。
- 信号捕捉:进程通过 signal() 函数捕捉信号,注册相应的信号处理函数。
- 信号处理:当信号到达进程时,如果该信号已被捕捉,则执行对应的信号处理函数。否则执行缺省处理动作。
- 信号返回:信号处理函数返回后,进程将继续执行被中断的代码。
3.2. 应用层
3.2.1 信号接收 signal函数
#include <singal.h>
void (*signal(int sig, void (*func)(int)))(int);
从内到外解析:
- void (*func)(int) : func 是函数指针,指向的参数是 int,返回 void。
- signal(int sig, void (*func)(int)) : signal() 函数接受两个参数,第一个参数是信号值 sig,第二个参数是函数指针 func。
- void (*)(int) : signal() 函数的返回值也是一个函数指针,指向的参数是 int,返回 void。
所以,理解起来比较直观的原型是:
如果把上面这个函数声明分解成两个部分就好理解了:
typedef void (* sign_handler_t) (int);
sign_handler_t signal(int sig , sign_handler handler);
举个例子,我们可以这么调用:
void handler(int sig) { ... }
sign_handler_t old_handler;
old_handler = signal(SIGINT, handler);
这表示:
- 定义了信号处理函数 handler()
- signal() 将 SIGINT 信号和 handler 函数关联
- old_handler 保存 signal() 返回的旧信号处理函数指针
- 这样我们就可以在 handler 函数中调用 old_handler,实现链式处理
3.2.2 应用层 fcntl 函数
fcntl() 函数用于操控文件描述符的属性,fcntl() 是 linux系统调用,用于设置和修改文件属性,也常用于文件锁定。它提供了一系列的操作来控制文件,是文件 I/O 过程中重要的系统调用之一。 原型如下:
int fcntl(int fd, int cmd, ... /* arg */ );
- fd:文件描述符
- cmd:命令,确定要执行的操作
- arg:第三个参数,根据cmd的不同,传入各种参数结构
fcntl() 支持的命令有:
命令 | 说明 |
---|---|
F_DUPFD | 复制文件描述符 |
F_GETFD | 获取文件描述符的 flag |
F_SETFD | 设置文件描述符的 flag |
F_GETFL | 获取文件状态标志(文件以什么模式打开的) |
F_SETFL | 设置文件状态标志 |
F_GETLK | 获取文件锁定信息 |
F_SETLK | 设置文件锁定(非阻塞) |
F_SETLKW | 设置文件锁定(阻塞) |
F_GETOWN | 获取接收SIGIO和SIGURG信号的进程ID或进程组ID |
F_SETOWN | 设置接收SIGIO和SIGURG信号的进程ID或进程组ID |
F_GETSIG | 获取产生SIGIO信号的事件 |
F_SETSIG | 设置产生SIGIO信号的事件 |
F_GETLEASE | 获取文件锁租约 |
F_SETLEASE | 设置文件锁租约 |
F_NOTIFY | 请求文件状态更改通知 |
F_CANCELLK | 取消文件上一整个共享锁许可(F_SETLK/F_SETLKW) |
F_DUPFD_CLOEXEC | 复制文件描述符并设置FD_CLOEXEC标志 |
F_SETPIPE_SZ | 设置管道大小 |
F_GETPIPE_SZ | 获取管道大小 |
F_ADD_SEALS | 添加Seals |
F_GET_SEALS | 获取Seals |
示例代码:
- 获取文件描述符标志:
int flag;
flag = fcntl(fd, F_GETFL);
- 设置文件为非阻塞:
int flag = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);
- 文件上锁(阻塞):
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0; // 锁整个文件
fcntl(fd, F_SETLKW, &lock);
- 文件解锁:
struct flock lock;
lock.l_type = F_UNLCK;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0; // 解锁整个文件
fcntl(fd, F_SETLK, &lock);
3.2.3 应用层信号驱动机制步骤
应用层完成SIGIO信号处理函数的注册后,并把设备文件的IO模式设置成信号驱动模式后,就可以去做其它事情了。驱动部分如果有事件发生,则会向应用层发信号SIGIO,这时应用层再切换到信号处理函数中去处理I/O工作。
第一步,完成信号处理函数
void xxx_handler(int signo){
//处理内容
}
第二步,通过signal函数注册信号处理函数,连接信号和信号处理函数
signal(SIGIO, input_handler);
第三步,通过F_SETOWN 控制命令设置该设备文件描述符 fd 的拥有者为当前进程,这样这个文件描述符就可以接收从设备驱动发出来的 SIGURG 和 SIGIO 信号。
fcntl(fd, F_SETOWN, getpid());
第四步,通过F_SETFL 控制命令设置设备文件以支持FASYNC,即异步通知模式。实质就是设置struct file里的f_flags。
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);
所以应用层的模板如下:
//应用模板
int main()
{
int fd = open("/dev/xxxx",O_RDONLY);
fcntl(fd, F_SETOWN, getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);
signal(SIGIO,xxxx_handler);
//......
}
void xxxx_handle(int signo)
{//读写数据
}
3.3 驱动层
3.3.1 驱动层模板
由于信号发出的源头在设备的驱动端,因此,必须在设备驱动程序中增加信号释放的相关代码。使驱动能在合适的时机释放信号。
这样,在驱动程序中涉及3项工作:
1、支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。此项工作已由内核完成,设备驱动无须处理。
2、支持F_SETFL命令处理,每当FASYNC标志改变时,驱动程序中的fasync函数将得以执行。因此,驱动中应该实现fasync()函数。
3、在设备资源可获得时,调用kill_fasync()函数激发相应的信号。
设备驱动中异步通知编程,主要用到一项数据结构和两个函数。数据结构是fasync_struct结构体,两个函数分别是:
1、处理FSYNC标志变更的函数
#include <linux/fs.h>
int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp)
2、释放信号用的函数
#include <linux/fs.h>
void kill_fasync(struct fasync_struct **fp, int sig, int band);
模板
1、在驱动的设备全局变量结构体里添加struct fasync_struct *async_queue
2、在struct file_operations 结构体内添加.fasync = xxx_fasync
3、完成xxx_fasync函数
4、在.release()函数里添加释放async_queue结构的代码
5、在对应的I/O操作函数里写上信号释放函数kill_fasync(),这里列举xxx_read函数,在设备有数据时,发出SIGIO指令:
3.3.2 驱动层 实现fasync函数
为了与应用层配合,驱动层需要完成 struct file_operations的成员.fasync函数的实现。其所涉及到的相关函数一一详述如下:
.fasync 成员是file_operations结构体中的一个函数指针,指向一个异步通知函数。
这个函数的原型是:
int (*fasync) (int fd, struct file *filp, int on)
- fd 是文件的文件描述符
- filp 是文件结构体指针
- on 是一个布尔值,表示是否要注册/解注册异步通知
这个函数的作用是:
- 当on为真时,注册一个异步通知。这意味着当对这个文件进行写操作时,内核会通知注册的进程。
- 当on为假时,解除异步通知的注册。
举个例子,在字符设备驱动中,当读操作results在数据可读时,我们可以调用fasync函数来通知读进程数据已经准备就绪,可以读取了。这样可以避免读进程调用阻塞读函数,提高效率。
一个使用fasync的简单例子:
static int mydev_fasync(int fd, struct file *filp, int on)
{
if (fasync_helper(fd, filp, on, &dev->async_queue))
return -EIO;
return 0;
}
struct file_operations mydev_fops = {
.read = mydev_read,
.fasync = mydev_fasync, // 使用fasync函数
}
那么当有数据可读时,我们可以(比如在.write函数里)调用:
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
来通知注册的读进程数据准备就绪。
3.3.3 fasync_helper
fasync_helper() 是内核提供的一个方便函数,它简化了fasync() 的实现。
它的原型是:
#include <linux/fs.h>
int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp)
它的作用是:
- 当on为真时,在*fapp链表中添加一个异步通知。这通常在file_operations的.fasync方法中调用。
- 当on为假时,从*fapp链表中删除一个异步通知。这通常在file_operations的.release方法中调用。
- 它会处理好链表操作,我们只需要传递&dev->async_queue这样的链表指针即可。
- 成功时返回0,失败返回-EIO。
如果要在struct file_operations结构的.release函数中释放异步通知结构对象:
fasync_helper(-1, filp, 0, &dev->pasync_obj);
3.3.4 struct fasync_struct
在上面一节,当我们调用fasync_helper(fd, filp, 1, &dev->async_queue)时:
- &dev->async_queue 是我们自己定义的一个链表结构体指针,原型:
struct fasync_struct *async_queue;
struct fasync_struct {
spinlock_t fa_lock;
int magic; //magic:用于检查该结构体是否有效,一般设为FASYNC
int fa_fd; //fa_fd: 触发异步通知的文件描述符
struct fasync_struct *fa_next; /* fa_next: 链表指针,用于链接多个异步通知回调 */
struct file *fa_file; // fa_file: 关联的文件结构体指针
struct rcu_head fa_rcu; // fa_rcu: RCU机制所需要的结构体,可以忽略
};
struct fasync_struct是用于表示单个异步通知回调的结构体,通过这些结构体,我们可以构建一个异步回调链表,并由内核提供的fasync_helper()和kill_fasync()来管理这个链表。
- 这个链表存放的是异步通知的回调函数和数据
- 当我们调用fasync_helper()并且on为1时,它会将filp添加到这个异步通知链表async_queue中
- 这意味着当我们调用kill_fasync()时,内核会调用该链表中的所有回调函数,并传入我们指定的信号量
所以,当我们说“把链表*fapp加入到异步通知”,意思是:
- *fapp指向我们定义的异步通知回调链表,比如async_queue
- 调用fasync_helper(fd, filp, 1, &async_queue)会将filp (就是struct file )这个文件结构体的回调添加到该链表
- 这样,当我们调用kill_fasync()时,filp的回调就会被调用,实现异步通知的效果
所以,fasync_helper() 简化了异步通知回调的管理,我们只需要传递一个回调链表的指针,它会处理好回调的添加和删除。
3.3.5 kill_fasync函数
kill_fasync() 是内核提供的一个函数,用于异步通知注册的进程。
它的原码是:
#include <linux/fs.h>
void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
/* First a quick test without locking: usually
* the list is empty.
*/
if (*fp) {
rcu_read_lock();
kill_fasync_rcu(rcu_dereference(*fp), sig, band);
rcu_read_unlock();
}
}
- *fp 是我们的异步通知回调链表,struct fasync_struct ** 指向保存异步通知结构地址的指针,比如 &dev->async_queue
- sig 是要发送的信号量,比如SIGIO/SIGKILL/SIGCHLD/SIGCONT/SIGSTOP
- band 指定哪种IO操作触发的通知,这里band参数可以填入以下值:
band值 | 含义 |
---|---|
POLL_IN | 数据可读事件,用于通知注册的进程数据已经可读 |
POLL_OUT | 数据可写事件,用于通知注册的进程设备已经可写 |
POLL_MSG | 消息可获得事件,用于通知注册的进程消息已经到达 |
POLL_ERR | 错误事件,用于通知注册的进程某个错误已经发生 |
POLL_PRI | 高优先级数据可读事件,用于通知注册的进程高优先级数据已经可读 |
POLL_HUP: | 设备挂起事件,用于通知注册的进程设备已经不可用 |
这个原型主要是调用了如下函数。这段代码定义在内核的fs/fctnl.c文件中。它的作用是遍历异步通知回调链表,并向每个回调发送SIGIO信号。
static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band) //fa指向异步通知回调链表,sig是SIGIO,band指定事件类型。
{
while (fa) { //循环遍历链表中的每个entry。
struct fown_struct *fown;
unsigned long flags;
if (fa->magic != FASYNC_MAGIC) { //校验fa的magic号,如果不正确则打印错误并返回。
printk(KERN_ERR "kill_fasync: bad magic number in "
"fasync_struct!\n");
return;
}
spin_lock_irqsave(&fa->fa_lock, flags); //获取fa的自旋锁,这是因为异步通知回调同时可能被不同线程访问。
if (fa->fa_file) { //如果fa有效且与之关联的文件也存在:
fown = &fa->fa_file->f_owner; //获取文件fown结构
if (!(sig == SIGURG && fown->signum == 0)) //检查信号是否是SIGURG,如果是但fown->signum为0则跳过(SIGURG有默认机制)
send_sigio(fown, fa->fa_fd, band); //调用send_sigio()发送SIGIO信号通知文件owner
}
spin_unlock_irqrestore(&fa->fa_lock, flags); //释放锁,移动到链表下一个entry。
fa = rcu_dereference(fa->fa_next);
}
}
当异步事件发生时,驱动调用kill_fasync():
- 该函数会遍历fp链表中的所有回调
- 对每个回调,向对应的进程发送sig信号
- 这样,注册的进程就收到通知,进行后续IO操作
举个简单例子:
struct fasync_struct *async_queue;
static int mydev_fasync(int fd, struct file *filp, int on)
{
if (fasync_helper(fd, filp, on, &async_queue))
return -EIO;
return 0;
}
static ssize_t mydev_read(struct file *filp, ...)
{
// ...
kill_fasync(&async_queue, SIGIO, POLL_IN);
// ...
}
在这个例子中:
- 当mydev_read()有数据可读时,它调用mydev_handler()
- mydev_handler()调用kill_fasync(),传入&async_queue异步通知链表
- kill_fasync()遍历该链表,对每个回调都发送SIGIO信号
- 注册的进程收到SIGIO信号,知道有数据可读,进行读取
所以,kill_fasync()是用来激活异步通知,通知注册进程某个事件已经发生的关键函数。当驱动有异步事件时,都需要调用kill_fasync()来通知进程。
3.4 实例
驱动程序
/*************************************************************************
> File Name:fasync-memory-1.c
驱动程序采用信号异步通知的驱动方式
************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/poll.h>
/*1、定义重要的变量及结构体*/
#define MEM_SIZE 500 //每个虚拟设备内存大小
#define DEV_NUM 3 //创建的设备总个数
struct mem_dev_t{
struct cdev my_dev; //cdev设备描述结构体变量
char mem[MEM_SIZE]; //fifo内存池,当成虚拟设备
int curpos; //内存当前数据最后位置指示,从0开始记
struct semaphore sem; //信号量
wait_queue_head_t write_queue; //写等待队列
wait_queue_head_t read_queue; //读等待队列
struct fasync_struct *async_queue ; //异步通知链表队列元素
};
struct mem_dev_t *mem_dev;
/*所有驱动函数声明*/
ssize_t read (struct file *, char __user *, size_t, loff_t *);
ssize_t write (struct file *, const char __user *, size_t, loff_t *);
int open (struct inode *, struct file *);
int release (struct inode *, struct file *);
int fasync (int, struct file *, int);
unsigned int poll (struct file *, struct poll_table_struct *);
//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={
.open = open,
.release = release,
.read = read,
.write = write,
.poll = poll,
.fasync = fasync,
};
/*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*/
/*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/
static int cdev_setup(struct mem_dev_t *mem_dev , dev_t devno ){
int unsucc =0;
cdev_init(&mem_dev->my_dev , &fops);
mem_dev->my_dev.owner = THIS_MODULE;
/*4、注册cdev结构体到内核链表中*/
unsucc = cdev_add(&mem_dev->my_dev , devno , 1);
if (unsucc){
printk("driver : cdev add faild \n");
return -1;
}
sema_init( &mem_dev->sem,1); //初始化信号量,为1
mem_dev->curpos = 0; //初始化缓冲数据位置为0
init_waitqueue_head(&mem_dev->write_queue);
init_waitqueue_head(&mem_dev->read_queue);
return 0;
}
static int __init my_init(void){
int major , minor;
dev_t devno;
int unsucc =0;
int i=0;
mem_dev = kzalloc(sizeof(struct mem_dev_t)*DEV_NUM , GFP_KERNEL);
if (!mem_dev){
printk(" driver : allocating memory is failed");
return -1;
}
/*2、创建 devno */
unsucc = alloc_chrdev_region(&devno , 0 , DEV_NUM , "select_memory");
if (unsucc){
printk(" driver : creating devno is failed\n");
return -1;
}else{
major = MAJOR(devno);
minor = MINOR(devno);
printk("diver : major = %d ; minor = %d\n",major,minor);
}
/*3、 初始化cdev结构体,并联cdev结构体与file_operations.*/
/*4、注册cdev结构体到内核链表中*/
for (i=0;i<DEV_NUM;i++){
devno = MKDEV(major , i);
if (cdev_setup(mem_dev+i , devno) == 0){
printk("deiver : the driver select_memory[%d] initalization completed\n", i);
} else
printk("deiver : the driver select_memory[%d] initalization failed\n", i);
}
return 0;
}
static void __exit my_exit(void)
{
int i=0;
dev_t devno;
devno = mem_dev->my_dev.dev;
for (i=0 ; i<DEV_NUM ; i++){
cdev_del(&(mem_dev+i)->my_dev);
}
unregister_chrdev_region(devno , DEV_NUM);
printk("***************the driver operate_memory exit************\n");
}
/*5、驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/
int open(struct inode *pnode , struct file *pf){
int minor = MINOR(pnode->i_rdev);
int major = MAJOR(pnode->i_rdev);
struct mem_dev_t *p = container_of(pnode->i_cdev , struct mem_dev_t , my_dev);
pf->private_data = p; //把全局变量指针放入到struct file结构体里
if (pf->f_flags & O_NONBLOCK){ //非阻塞
printk("driver : select_memory[%d , %d] is opened by nonblock mode\n",major , minor);
}else{
printk("driver : select_memory[%d , %d] is opened by block mode\n",major,minor);
}
return 0;
}
/*file_operations结构全成员函数.release的具体实现*/
int release(struct inode *pnode , struct file *pf){
struct mem_dev_t *p = pf->private_data;
printk("select_memory is closed \n");
if (&p->async_queue)
fasync_helper(-1, pf , 0 , &p->async_queue);
return 0;
}
/*file_operations结构全成员函数.read的具体实现*/
ssize_t read (struct file * pf, char __user * buf, size_t size , loff_t * ppos){
//本例中,因为是fifo,所以ppos参数不用。
struct mem_dev_t *pdev = pf->private_data;
int count = 0; //存储读到多少数据
int ret = 0;
DECLARE_WAITQUEUE(wait , current); //定义等待队列项目元素
down(&pdev->sem);
add_wait_queue(&pdev->read_queue , &wait); //把元素加入读等待队列
while (pdev->curpos == 0){
if ((pf->f_flags & O_NONBLOCK) == 0){
//当前没有数据,进入阻塞睡眠
set_current_state(TASK_INTERRUPTIBLE); //设置当前进程为可中断睡眠态
up(&pdev->sem); //退出前释放信号量,V操作
schedule(); //调度程序
}else{
ret = 0;
goto out;
}
down(&pdev->sem);
}
//判断能够读到的字节数量
printk ("begin size=%d , curpos = %d\n" , size , pdev->curpos);
if (size > pdev->curpos){
count = pdev->curpos;
}else{
count = size;
}
//copy_from_user返回值大于0失败
if ( copy_to_user(buf , &pdev->mem , count )){ //读取失败
ret = 0;
goto out;
}else{ //成功读取
memcpy(&pdev->mem , &pdev->mem[count] , pdev->curpos-count);
pdev->curpos -= count;
printk ("begin count =%d , curpos = %d\n" , count , pdev->curpos);
up(&pdev->sem); //退出前释放信号量,V操作
wake_up_interruptible(&pdev->write_queue); //唤醒可能睡眠的write
ret = count;
}
out:
up(&pdev->sem); //退出前释放信号量,V操作
remove_wait_queue(&pdev->read_queue , &wait);
set_current_state(TASK_RUNNING);
return ret;
}
/*file_operations结构全成员函数.write的具体实现*/
ssize_t write (struct file * pf, const char __user *buf, size_t size , loff_t *ppos){
struct mem_dev_t *pdev = pf -> private_data;
int count =0;
int ret = 0;
DECLARE_WAITQUEUE(wait , current); //定义等待队列项目元素
down(&pdev->sem);
add_wait_queue(&pdev->write_queue , &wait); //把元素加入读等待队列
while (pdev->curpos == (MEM_SIZE -1)){
if ((pf->f_flags & O_NONBLOCK) == 0){
set_current_state(TASK_INTERRUPTIBLE);
up(&pdev->sem);
schedule();
}else{
ret = 0;
goto out;
}
down(&pdev->sem);
}
if (size > (MEM_SIZE-pdev->curpos)){
count = MEM_SIZE-pdev->curpos;
}else{
count = size;
}
if (copy_from_user(&pdev->mem[pdev->curpos],buf,count)){
ret = 0;
goto out;
}else{
pdev->curpos +=count;
wake_up_interruptible(&pdev->read_queue);
if (&pdev->async_queue)
kill_fasync(&pdev->async_queue , SIGIO, POLL_IN);
ret = count;
}
out:
up(&pdev->sem);
remove_wait_queue(&pdev->write_queue, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
unsigned int poll (struct file *pf, struct poll_table_struct * pts){
struct mem_dev_t *p = pf->private_data;
unsigned int mark = 0;
poll_wait(pf, &p->read_queue , pts);
poll_wait(pf , &p->write_queue , pts);
//测试这里是否是阻塞还是轮询
//printk("poll in waiting......or .....poll.");
//
if (p->curpos > 0){
mark |= POLLIN | POLLRDNORM;
}
#if 0
if (p->curpos < MEM_SIZE-1){
mark |= POLLOUT | POLLWRNORM;
}
#endif
return mark;
}
int fasync (int fd, struct file *pfile, int on){
struct mem_dev_t *p = pfile->private_data;
return fasync_helper(fd , pfile , on , &p->async_queue);
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
应用层
/*************************************************************************
> File Name: op_mem.c
************************************************************************/
#include<stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <signal.h>
int fd = 0;
void sign_handler(int sign){
char buf[20] = {0};
if (sign = SIGIO){
read(fd , buf , 20);
printf ("buf = %s\n", buf);
}
}
int main(int argc , char **argv){
unsigned int flag;
if (argc < 2){
printf("argument is less!\n");
return 0;
}
fd = open(argv[1] , O_RDWR|O_APPEND );
if (fd < 0){
perror("open ");
}
signal(SIGIO , sign_handler);
fcntl(fd , F_SETOWN , getpid());
flag = fcntl(fd , F_GETFL);
flag |= FASYNC;
fcntl(fd , F_SETFL , FASYNC);
while (1);
close(fd);
return 0;
}