【嵌入式环境下linux内核及驱动学习笔记-(8-内核 I/O)-信号驱动】

目录

  • 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>
信号值信号名说明
1SIGHUP终端断线,用于进程与控制终端的连接
2SIGINT键盘中断,用于进程的输入终端
3SIGQUIT键盘的退出键(Ctrl+/)
4SIGILL非法指令
5SIGTRAP跟踪陷阱
6SIGABRT由abort(3)发出的异常终止条件信号
7SIGBUS总线错误
8SIGFPE算术运算错误异常
9SIGKILL强制终止进程
10SIGUSR1用户自定义信号1
11SIGSEGV无效内存引用
12SIGUSR2用户自定义信号2
13SIGPIPE向一个没有读取端口的管道或套接字写入数据
14SIGALRM时钟定时信号
15SIGTERM终止进程的信号
16SIGSTKFLT协处理器堆栈错误
17SIGCHLD子进程停止或退出时向其父进程发送的信号
18SIGCONT如果进程已停止,则继续执行
19SIGSTOP停止进程运行(不能被捕获、阻塞或忽略)
20SIGTSTP用户键入susp字符(通常是Ctrl+Z)
21SIGTTIN后台进程企图从控制终端读入
22SIGTTOU后台进程企图写出到控制终端
23SIGURG由套接字上的紧急数据到达产生
24SIGXCPU超过CPU时间限制
25SIGXFSZ超过文件大小限制
26SIGVTALRM虚拟时钟定时信号
27SIGPROF定时器信号
28SIGWINCH窗口大小更改
29SIGIOI/O准备就绪
30SIGPWR电源错误
31SIGSYS错误的参数或错误的 syscall 发生

3.1.2 信号的工作流程:

  1. 产生信号:通过 kill 命令向进程发送信号,或按 Ctrl+C 键盘中断。
  2. 信号递送:内核向目标进程递送信号。
  3. 信号捕捉:进程通过 signal() 函数捕捉信号,注册相应的信号处理函数。
  4. 信号处理:当信号到达进程时,如果该信号已被捕捉,则执行对应的信号处理函数。否则执行缺省处理动作。
  5. 信号返回:信号处理函数返回后,进程将继续执行被中断的代码。
    在这里插入图片描述

3.2. 应用层

3.2.1 信号接收 signal函数

#include <singal.h>

void (*signal(int sig, void (*func)(int)))(int);

从内到外解析:

  1. void (*func)(int) : func 是函数指针,指向的参数是 int,返回 void。
  2. signal(int sig, void (*func)(int)) : signal() 函数接受两个参数,第一个参数是信号值 sig,第二个参数是函数指针 func。
  3. 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/15527.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从历史天气预报 API 看气象大数据的商业价值

引言 近年来&#xff0c;随着气象观测技术的不断提升和气象大数据的快速发展&#xff0c;越来越多的企业开始将气象数据应用于商业领域。其中&#xff0c;历史天气预报 API 作为一种可获取历史气象数据的接口&#xff0c;具有广泛的商业应用价值。 本文将从历史天气预报 API …

Delphi 知识 彻底搞懂Delphi中的匿名方法

前言&#xff1a; 顾名思义&#xff0c;匿名方法是一个没有与之相关的名字的过程或函数。一个匿名方法将一个代码块视为一个实体&#xff0c;可以分配给一个变量或作为一个方法的参数使用。此外&#xff0c;匿名方法可以引用变量&#xff0c;并在定义该方法的上下文中为变量绑定…

【Unity入门】19.定时调用Invoke

【Unity入门】定时调用Invoke 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity入门系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;计时器 &#xff08;1&#xff09;Invoke 单词调用 计时器我们并不陌生&#xff0c;在cocos上有着schedule类是…

056:cesium 七种方法设置颜色

第056个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中设置颜色,这里用到了7种方法,查看API,还有很多种方法 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共115行)相关API参考:专栏目标示例效果 配置…

【C++】map和set的模拟实现

一、思路 1. 改造RBTree 现在我们有一棵 R B T r e e RBTree RBTree&#xff0c;那么如何用它实现 m a p map map和 s e t set set&#xff1f;我们知道 m a p map map是 KV 结构&#xff0c; s e t set set是 K 结构&#xff0c;传统思路是两份 R B T r e e RBTree RBTree的代…

【MATLAB图像处理实用案例详解(12)】——利用BP神经网络实现图像压缩

目录 一、图像压缩二、BP神经网络实现图像压缩原理三、算法步骤3.1 图像块划分3.2 归一化3.3 建立BP神经网络3.4 保存结果 四、效果演示 一、图像压缩 常见的文件压缩软件如WinZip、WinRAR等采用的是无损压缩&#xff0c;能够完全恢复原文件内容。多媒体信息具有信息量大、冗余…

STM32F4 HAL库使用DMA进行ADC采样实时发送波形到串口显示(包含傅里叶变换)

1.总体逻辑 按下STM32F4的KEY0按键&#xff0c;通过外部中断的方式对按键进行检测&#xff0c;然后开启一次带DMA的固定点数的ADC采集&#xff0c;采集完成后在DMA的中断发送采集到的数据&#xff0c;然后清空数据区准备下一次的按键中断。电脑接受到串口数据后对数据进行简单…

【JavaEE】SpringBoot的日志

目录 日志作用 SpringBoot日志框架 日志打印 日志级别 类型 作用 修改级别 日志永久化 配置日志文件目录 配置日志文件名 简化日志打印和永久化——lombok 日志作用 问题定位&#xff1a;可以帮助开发人员快速找到问题出现的位置系统监控&#xff1a;可以把系统的运…

跳跃游戏类题目 总结篇

一.跳跃游戏类题目简单介绍 跳跃游戏是一种典型的算法题目&#xff0c;经常是给定一数组arr&#xff0c;从数组的某一位置i出发&#xff0c;根据一定的跳跃规则&#xff0c;比如从i位置能跳arr[i]步&#xff0c;或者小于arr[i]步&#xff0c;或者固定步数&#xff0c;直到到达某…

结构型模式-组合模式

组合模式 概述 ​ 对于这个图片肯定会非常熟悉&#xff0c;上图我们可以看做是一个文件系统&#xff0c;对于这样的结构我们称之为树形结构。在树形结构中可以通过调用某个方法来遍历整个树&#xff0c;当我们找到某个叶子节点后&#xff0c;就可以对叶子节点进行相关的操作。…

计算机组成原理4.2.2汉明码

编码的最小距离 奇校验和偶校验 看1的个数是奇数 还是偶数 汉明码 汉明码的配置 根据不等式&#xff0c;确定增添几位&#xff0c;根据指数放置增添位 汉明码的检错 分不同检测小组 分组规则&#xff1a;哪位为’1‘就是哪组元素。 1号位为‘1’的都是第一组元素&#…

基于COM组件实现C#调用C++类对象过程中的注意事项

目录 一、基于COM的调用原理二、注意事项如何在C ATL中有效添加方法与属性如何让C#调用C中的属性&#xff08;.idl中声明属性&#xff09;如何对变量类型进行转换C#如何获取C类中的参数变量 一、基于COM的调用原理 调用原理&#xff1a;首先基于C ATL模板类&#xff0c;实现需…

【网络进阶】服务器模型Reactor与Proactor

文章目录 1. Reactor模型2. Proactor模型3. 同步IO模拟Proactor模型 在高并发编程和网络连接的消息处理中&#xff0c;通常可分为两个阶段&#xff1a;等待消息就绪和消息处理。当使用默认的阻塞套接字时&#xff08;例如每个线程专门处理一个连接&#xff09;&#xff0c;这两…

【redis】redis分布式锁(二)可重入锁+设计模式

【redis】redis分布式锁&#xff08;二&#xff09;可重入锁 文章目录 【redis】redis分布式锁&#xff08;二&#xff09;可重入锁前言一、可重入锁&#xff08;又名递归锁&#xff09;1、说明&#xff1a;2、分开解释&#xff1a;3、可重入锁的种类隐式锁&#xff08;即synch…

【软件测试】测试用例的设计

文章目录 一. 针对没有需求的案例来设计测试用例二. 针对有需求的案例来设计测试用例1. 穷举法2. 等价类3. 边界值4. 判定表法5. 场景设计法5.1 简介5.2 基本设计步骤5.3 基本流和备选流5.4 使用场景5.5 优缺点5.6 实例 6. 错误猜测法 一. 针对没有需求的案例来设计测试用例 针…

深度强化学习——蒙特卡洛算法(6)

注&#xff1a;本章的内容作为补充插曲&#xff0c;大家可以选看&#xff0c;不过还是建议把最后一个使用蒙特卡洛近似求期望稍微看一下 蒙特卡洛是一大堆随机算法&#xff0c;通过随机样本来估算真实值 使用随机样本来近似Π 1、在[a,b]做随机均匀抽样&#xff0c;抽出n个样…

YOLO物体检测系列1.经典方法概述及评价指标体现

1. 深度学习经典检测方法&#xff1a; two-stage(两阶段)&#xff1a; Faster-rcnn Mask-RCNN系列 one-stage(单阶段)&#xff1a;Yolo系列 两阶段&#xff1a;一阶段实现RPN候选区域预选 二阶段基于候选区域再进行检测回归分类任务 单阶段&#xff1a;一个CNN卷积网络实现检测…

C++线程的简单学习及了解

此篇文章只是线程的简单了解。 文章目录 前言一、线程的优缺点二、C线程库 1.thread类的简单介绍2.线程函数参数总结 前言 什么是线程&#xff1f; 在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程是“一个进程内部的控…

day3 TCP/IP协议与五层体系结构

TCP / IP 四层体系结构 TCP / IP工作流程&#xff1a; 现在互联网使用的 TCP/IP 体系结构已经发生了演变&#xff0c;即某些应用程序可以直接使用 IP 层&#xff0c;或甚至直接使用最下面的网络接口层。 沙漏型展示&#xff1a; 五层体系结构 各层的主要功能 应用层&#xff1…

搭建外网minecraft服务器方案

很多minecraft服务器主都想自己搭建一个外网可以访问的minecraft服务器&#xff0c;在没有外网IP的情况下&#xff0c;一般都是使用Logmein Hamachi方案。这种方案有它的弊端&#xff0c;需要客户机安装Hamachi&#xff0c;十分不方便。另外&#xff0c;免费版只支持5人&#x…