【Linux-阻塞,非阻塞,异步】

Linux-阻塞,非阻塞,异步

  • ■ Linux-阻塞-非阻塞 IO-异步
  • ■ Linux-阻塞IO
    • ■ 阻塞IO简介
    • ■ open
    • ■ 等待队列
    • ■ 示例一:APP阻塞IO读取进入睡眠后-等待队列唤醒进程
  • ■ Linux-非阻塞IO
    • ■ 非阻塞IO简介
    • ■ open
    • ■ 轮询
      • ■ 1、select 函数
      • ■ 2、poll 函数
      • ■ 3、epoll 函数
  • ■ 异步通知-信号
    • ■ 示例一:CTRL+C 组合键以后先在终端上打印出“SIGINT signal!”
    • ■ 驱动中的信号处理
      • ■ 1、 fasync_struct 结构体
      • ■ 2、 fasync 函数
      • ■ 3、 kill_fasync 函数

■ Linux-阻塞-非阻塞 IO-异步

在这里插入图片描述

■ Linux-阻塞IO

■ 阻塞IO简介

在这里插入图片描述

■ open

fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */

■ 等待队列

进程阻塞访问IO-》进程阻塞进入休眠-》等待队列-》 唤醒进程。
阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU 资源让出来。
但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。

Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作。
等待队列头使用结构体wait_queue_head_t 表示, wait_queue_head_t 结构体定义在文件 include/linux/wait.h 中,

  1. 等待队列头
struct __wait_queue_head {
	spinlock_t lock;
	struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

void init_waitqueue_head(wait_queue_head_t *q)  //q 就是要初始化的等待队列头。
  1. 等待队列项
struct __wait_queue {
	unsigned int flags;
	void *private;
	wait_queue_func_t func;
	struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;

DECLARE_WAITQUEUE(name, tsk)  //初始化一个等待队列项
	name 就是等待队列项的名字, 
	tsk 表示这个等待队列项属于哪个任务(进程),一般设置为current 

  1. 将队列项添加/移除等待队列头
    当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,只有添加到等待队列头中以后进程才能进入休眠态。
    当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可.
void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
	q: 等待队列项要加入的等待队列头。
	wait:要加入的等待队列项。
	
void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
	q: 要删除的等待队列项所处的等待队列头。
	wait:要删除的等待队列项。
  1. 等待唤醒
    当设备可以使用的时候就要唤醒进入休眠态的进程。这两个函数会将这个等待队列头中的所有进程都唤醒。
void wake_up(wait_queue_head_t *q)     //可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的进程
void wake_up_interruptible(wait_queue_head_t *q)  //只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程。
	q 就是要唤醒的等待队列头
  1. 等待事件
    除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程
1 wait_event(wq, condition)
等待以 wq 为等待队列头的等待队列被唤醒,前提是 condition 条件必须满足(为真),否则一直阻塞 。
此 函 数 会 将 进 程 设 置 为TASK_UNINTERRUPTIBLE 状态
TASK_UNINTERRUPTIBLE状态是一种不可中断的睡眠状态,不可以被信号打断,必须等到等待的条件满足时才被唤醒。

2 wait_event_timeout(wq, condition, timeout)
功能和 wait_event 类似,但是此函数可以添加超时时间,以 jiffies 为单位。此函数有返回值,
如果
返回 0 的话表示超时时间到,而且 condition为假。
返回 1 的话表示 condition 为真,也就是条件满足了。

3 wait_event_interruptible(wq, condition)
与 wait_event 函数类似,但是此函数将进程设置为 TASK_INTERRUPTIBLE,就是可以被信号打断。

4 wait_event_interruptible_timeout(wq,condition, timeout)
与 wait_event_timeout 函数类似,此函数也将进程设置为 TASK_INTERRUPTIBLE,可以被信号打断。

■ 示例一:APP阻塞IO读取进入睡眠后-等待队列唤醒进程

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT		1			/* 设备号个数 	*/
#define IMX6UIRQ_NAME		"blockio"	/* 名字 		*/
#define KEY0VALUE			0X01		/* KEY0按键值 	*/
#define INVAKEY				0XFF		/* 无效的按键值 */
#define KEY_NUM				1			/* 按键数量 	*/

/* 中断IO描述结构体 */
struct irq_keydesc {
	int gpio;								/* gpio */
	int irqnum;								/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
};

/* imx6uirq设备结构体 */
struct imx6uirq_dev{
	dev_t devid;			/* 设备号 	 */	
	struct cdev cdev;		/* cdev 	*/                 
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */	
	atomic_t keyvalue;		/* 有效的按键键值 */
	atomic_t releasekey;	/* 标记是否完成一次完成的按键,包括按下和释放 */
	struct timer_list timer;/* 定义一个定时器*/
	struct irq_keydesc irqkeydesc[KEY_NUM];	/* 按键init述数组 */
	unsigned char curkeynum;				/* 当前init按键号 */

	wait_queue_head_t r_wait;	/* 读等待队列头 */
};

struct imx6uirq_dev imx6uirq;	/* irq设备 */

/* @description		: 中断服务函数,开启定时器		
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
	struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id;

	dev->curkeynum = 0;
	dev->timer.data = (volatile long)dev_id;
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	/* 10ms定时 */
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description	: 定时器服务函数,用于按键消抖,定时器到了以后
 *				  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
 * @param - arg	: 设备结构变量
 * @return 		: 无
 */
void timer_function(unsigned long arg)
{
	unsigned char value;
	unsigned char num;
	struct irq_keydesc *keydesc;
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

	num = dev->curkeynum;
	keydesc = &dev->irqkeydesc[num];

	value = gpio_get_value(keydesc->gpio); 	/* 读取IO值 */
	if(value == 0){ 						/* 按下按键 */
		atomic_set(&dev->keyvalue, keydesc->value);
	}
	else{ 									/* 按键松开 */
		atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
		atomic_set(&dev->releasekey, 1);	/* 标记松开按键,即完成一次完整的按键过程 */
	}               

	/* 唤醒进程 */
	if(atomic_read(&dev->releasekey)) {	/* 完成一次按键过程 */
		/* wake_up(&dev->r_wait); */
		wake_up_interruptible(&dev->r_wait);
	}
}

/*
 * @description	: 按键IO初始化
 * @param 		: 无
 * @return 		: 无
 */
static int keyio_init(void)
{
	unsigned char i = 0;
	char name[10];
	int ret = 0;
	
	imx6uirq.nd = of_find_node_by_path("/key");
	if (imx6uirq.nd== NULL){
		printk("key node not find!\r\n");
		return -EINVAL;
	} 

	/* 提取GPIO */
	for (i = 0; i < KEY_NUM; i++) {
		imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
		if (imx6uirq.irqkeydesc[i].gpio < 0) {
			printk("can't get key%d\r\n", i);
		}
	}
	
	/* 初始化key所使用的IO,并且设置成中断模式 */
	for (i = 0; i < KEY_NUM; i++) {
		memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name));	/* 缓冲区清零 */
		sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);		/* 组合名字 */
		gpio_request(imx6uirq.irqkeydesc[i].gpio, name);
		gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);	
		imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
		imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
	}

	/* 申请中断 */
	imx6uirq.irqkeydesc[0].handler = key0_handler;
	imx6uirq.irqkeydesc[0].value = KEY0VALUE;
	
	for (i = 0; i < KEY_NUM; i++) {
		ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, 
		                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
		if(ret < 0){
			printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
			return -EFAULT;
		}
	}

	/* 创建定时器 */
     init_timer(&imx6uirq.timer);
     imx6uirq.timer.function = timer_function;

	/* 初始化等待队列头 */
	init_waitqueue_head(&imx6uirq.r_wait);
	return 0;
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &imx6uirq;	/* 设置私有数据 */
	return 0;
}

 /*
  * @description     : 从设备读取数据 
  * @param - filp    : 要打开的设备文件(文件描述符)
  * @param - buf     : 返回给用户空间的数据缓冲区
  * @param - cnt     : 要读取的数据长度
  * @param - offt    : 相对于文件首地址的偏移
  * @return          : 读取的字节数,如果为负值,表示读取失败
  */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret = 0;
	unsigned char keyvalue = 0;
	unsigned char releasekey = 0;
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

#if 0
	/* 加入等待队列,等待被唤醒,也就是有按键按下 */
 	ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); 
	if (ret) {
		goto wait_error;
	} 
#endif

	DECLARE_WAITQUEUE(wait, current);	        /* 定义一个等待队列 */
	if(atomic_read(&dev->releasekey) == 0) {	/* 没有按键按下 */
		add_wait_queue(&dev->r_wait, &wait);	/* 将等待队列添加到等待队列头 */
		__set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态 */
		schedule();							    /* 进行一次任务切换 */
		if(signal_pending(current))	{			/* 判断是否为信号引起的唤醒 */
			ret = -ERESTARTSYS;
			goto wait_error;
		}
		__set_current_state(TASK_RUNNING);         /* 将当前任务设置为运行状态 */
	    remove_wait_queue(&dev->r_wait, &wait);    /* 将对应的队列项从等待队列头删除 */
	}

	keyvalue = atomic_read(&dev->keyvalue);
	releasekey = atomic_read(&dev->releasekey);

	if (releasekey) { /* 有按键按下 */	
		if (keyvalue & 0x80) {
			keyvalue &= ~0x80;
			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
		} else {
			goto data_error;
		}
		atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
	} else {
		goto data_error;
	}
	return 0;

wait_error:
	set_current_state(TASK_RUNNING);		/* 设置任务为运行态 */
	remove_wait_queue(&dev->r_wait, &wait);	/* 将等待队列移除 */
	return ret;

data_error:
	return -EINVAL;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
	.owner = THIS_MODULE,
	.open = imx6uirq_open,
	.read = imx6uirq_read,
};

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init imx6uirq_init(void)
{
	/* 1、构建设备号 */
	if (imx6uirq.major) {
		imx6uirq.devid = MKDEV(imx6uirq.major, 0);
		register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
	} else {
		alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
		imx6uirq.major = MAJOR(imx6uirq.devid);
		imx6uirq.minor = MINOR(imx6uirq.devid);
	}

	/* 2、注册字符设备 */
	cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
	cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);

	/* 3、创建类 */
	imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
	if (IS_ERR(imx6uirq.class)) {	
		return PTR_ERR(imx6uirq.class);
	}

	/* 4、创建设备 */
	imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
	if (IS_ERR(imx6uirq.device)) {
		return PTR_ERR(imx6uirq.device);
	}
		
	/* 5、始化按键 */
	atomic_set(&imx6uirq.keyvalue, INVAKEY);
	atomic_set(&imx6uirq.releasekey, 0);
	keyio_init();
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit imx6uirq_exit(void)
{
	unsigned i = 0;
	/* 删除定时器 */
	del_timer_sync(&imx6uirq.timer);	/* 删除定时器 */
		
	/* 释放中断 */	
	for (i = 0; i < KEY_NUM; i++) {
		free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
		gpio_free(imx6uirq.irqkeydesc[i].gpio);
	}
	cdev_del(&imx6uirq.cdev);
	unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
	device_destroy(imx6uirq.class, imx6uirq.devid);
	class_destroy(imx6uirq.class);
}
	
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");	
	

阻塞访问测试APP

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd;
	int ret = 0;
	char *filename;
	unsigned char data;

	if (argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR);
	if (fd < 0) {
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

	while (1) {
		ret = read(fd, &data, sizeof(data));
		if (ret < 0) {  /* 数据读取错误或者无效 */
			
		} else {		/* 数据读取正确 */
			if (data)	/* 读取到数据 */
				printf("key value = %#X\r\n", data);
		}
	}
	close(fd);
	return ret;
}

■ Linux-非阻塞IO

■ 非阻塞IO简介

在这里插入图片描述

■ open

fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); 
/* 非阻塞方式打开 表示以非阻塞方式打开设备,这样从设备中读取数据的时候就是非阻塞方式*/

■ 轮询

如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。 poll、 epoll 和 select 可以用于处理轮询,

■ 1、select 函数

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout)
	nfds: 所要监视的这三类文件描述集合中, 最大文件描述符加 1。
	
	readfds、 writefds 和 exceptfds:这三个指针指向描述符集合,
	
	readfds 用于监视指定描述符集的读变化,也就是监视这些文件是否可以读取,只要这些集合里面有一个文件可以读取那么 seclect 就会返回一个大于 0 的值表示文件可以读取。如果没有文件可以读取,那么就会根据 timeout 参数来判断是否超时。
    可以将 readfs设置为 NULL,表示不关心任何文件的读变化
   
    exceptfds 用于监视这些文件的异常:
		void FD_ZERO(fd_set *set)          用于将 fd_set 变量的所有位都清零
		void FD_SET(int fd, fd_set *set)   用于将 fd_set 变量的某个位置 1,也就是向 fd_set 添加一个文件描述符,参数 fd 就是要加入的文件描述符
		void FD_CLR(int fd, fd_set *set)   用于将 fd_set 变量的某个位清零,也就是将一个文件描述符从 fd_set 中删除,参数 fd 就是要删除的文件描述符
		int FD_ISSET(int fd, fd_set *set)  用于测试一个文件是否属于某个集合,参数 fd 就是要判断的文件描述符。
	
	timeout:超时时间:
		struct timeval {
			long tv_sec; /* 秒 */
			long tv_usec; /* 微妙 */
		};

示例程序

 void main(void)
 {
	int ret, fd; /* 要监视的文件描述符 */
	fd_set readfds; /* 读操作文件描述符集 */
	struct timeval timeout; /* 超时结构体 */
	
	fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
	FD_ZERO(&readfds); /* 清除 readfds */
	FD_SET(fd, &readfds); /* 将 fd 添加到 readfds 里面 */

	/* 构造超时时间 */
	timeout.tv_sec = 0;
	timeout.tv_usec = 500000; /* 500ms */

	ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
	switch (ret) {
	case 0: /* 超时 */
		printf("timeout!\r\n");
		break;
	case -1: /* 错误 */
		printf("error!\r\n");
		break;
	default: /* 可以读取数据 */
		if(FD_ISSET(fd, &readfds)) { /* 判断是否为 fd 文件描述符 */
		/* 使用 read 函数读取数据 */
		}
	break;
	}
}

■ 2、poll 函数

int poll(struct pollfd *fds,nfds_t nfds,int timeout)
fds: 要监视的文件描述符集合以及要监视的事件,为一个数组,数组元素都是结构体 pollfd类型的
	struct pollfd {
		int fd; /* 文件描述符 */
		short events; /* 请求的事件 */      events 是要监视的事件
		short revents; /* 返回的事件 */     revents 是返回参数,也就是返回的事件,
	};
	events: 
		POLLIN 有数据可以读取。
		POLLPRI 有紧急的数据需要读取。
		POLLOUT 可以写数据。
		POLLERR 指定的文件描述符发生错误。
		POLLHUP 指定的文件描述符挂起。
		POLLNVAL 无效的请求。
		POLLRDNORM 等同于 POLLIN
nfds: poll 函数要监视的文件描述符数量。
timeout: 超时时间,单位为 ms。

返回值:返回 revents 域中不为 0 的 pollfd 结构体个数,也就是发生事件或错误的文件描述符数量; 0,超时; -1,发生错误,并且设置 errno 为错误类型。

示例程序

void main(void)
{
	int ret;
	int fd; /* 要监视的文件描述符 */
	struct pollfd fds;
	fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */

	/* 构造结构体 */
	fds.fd = fd;
	fds.events = POLLIN; /* 监视数据是否可以读取 */
	ret = poll(&fds, 1, 500); /* 轮询文件是否可操作,超时 500ms */
	if (ret) { /* 数据有效 */
		......
		/* 读取数据 */
		......
	} else if (ret == 0) { /* 超时 */
		......
	} else if (ret < 0) { /* 错误 */
		......
	}
}

■ 3、epoll 函数

■ 异步通知-信号

异步通知的核心就是信号,在 arch/xtensa/include/uapi/asm/signal.h 文件中定义了 Linux 所支持的所有信号

#define SIGHUP 1 /* 终端挂起或控制进程终止 */
#define SIGINT 2 /* 终端中断(Ctrl+C 组合键) */
#define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
#define SIGILL 4 /* 非法指令 */
#define SIGTRAP 5 /* debug 使用,有断点指令产生 */
#define SIGABRT 6 /* 由 abort(3)发出的退出指令 */
#define SIGIOT 6 /* IOT 指令 */
#define SIGBUS 7 /* 总线错误 */
#define SIGFPE 8 /* 浮点运算错误 */
#define SIGKILL 9 /* 杀死、终止进程 */
#define SIGUSR1 10 /* 用户自定义信号 1 */
#define SIGSEGV 11 /* 段违例(无效的内存段) */
#define SIGUSR2 12 /* 用户自定义信号 2 */
#define SIGPIPE 13 /* 向非读管道写入数据 */
#define SIGALRM 14 /* 闹钟 */
#define SIGTERM 15 /* 软件终止 */
#define SIGSTKFLT 16 /* 栈异常 */
#define SIGCHLD 17   /* 子进程结束 */

SIGKILL(9)和 SIGSTOP(19)这两个信号不能被忽略外,其他的信号都可以忽略。
这些信号就相当于中断号,不同的中断号代表了不同的中断。
驱动程序可以通过向应用程序发送不同的信号来实现不同的功能。

sighandler_t signal(int signum, sighandler_t handler)
	signum:要设置处理函数的信号。
	handler: 信号的处理函数。

信号处理函数原型
typedef void (*sighandler_t)(int)

■ 示例一:CTRL+C 组合键以后先在终端上打印出“SIGINT signal!”

当按下键盘上的 CTRL+C 组合键以后会向当前正在占用终端的应用程序发出 SIGINT 信号, SIGINT 信号默认的动作是关闭当前应用程序。这里我们修改一下 SIGINT 信号的默认处理函数,
当按下 CTRL+C 组合键以后先在终端上打印出“SIGINT signal!”

#include "stdlib.h"
#include "stdio.h"
#include "signal.h"

void sigint_handler(int num)
{
	printf("\r\nSIGINT signal!\r\n");
	exit(0);
}

int main(void)
{
	signal(SIGINT, sigint_handler);
	while(1);
	return 0;
}

■ 驱动中的信号处理

■ 1、 fasync_struct 结构体

struct fasync_struct {
	spinlock_t fa_lock;
	int magic;
	int fa_fd;
	struct fasync_struct *fa_next;
	struct file *fa_file;
	struct rcu_head fa_rcu;
};

■ 2、 fasync 函数

int (*fasync) (int fd, struct file *filp, int on)
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

驱动中编写fasync 函数

struct xxx_dev {
	......
	struct fasync_struct *async_queue; /* 异步相关结构体 */
};

static int xxx_fasync(int fd, struct file *filp, int on)
{
	struct xxx_dev *dev = (xxx_dev)filp->private_data;

	if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
	return -EIO;
	return 0;
}

static struct file_operations xxx_ops = {
	......
	.fasync = xxx_fasync,
	......
};

■ 3、 kill_fasync 函数

驱动程序需要向应用程序发出信号。

void kill_fasync(struct fasync_struct **fp, int sig, int band)
	fp:要操作的 fasync_struct。
	sig: 要发送的信号。
	band: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT。

在这里插入代码片

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

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

相关文章

【轻触按键】开关机电路--填坑1

接上文&#xff0c;挖的坑 一、翻转电路 二、真值表 按键按下去&#xff0c;1G17会拉低&#xff0c;A端脚会掉电&#xff0c;下降沿&#xff1b;终到逻辑“0” 松开按键&#xff0c;1G17会拉高&#xff0c;A端脚充电&#xff0c;上升沿&#xff1b;终到逻辑“1”&#xff1b; …

[补题记录]LeetCode 6.Z字形变换

传送门&#xff1a;Z字形变换 转自&#xff1a;Z字形变换 Thought/思路 关键点在于&#xff0c;最后的答案是一行行连接起来的。 这样我们就会发现&#xff0c;这个 Z 字&#xff0c;实际上会让行数 不断加 1&#xff0c;然后又 不断减 1。每次按顺序选择 S 中的一个字符即…

visual studio打包qt算子时,只生成dll没有生成lib等文件

问题&#xff1a;在visual studio配置了qt项目&#xff0c;并打包成dll&#xff0c;原则上会生成一堆文件&#xff0c;包括dll,lib等文件。 解决办法&#xff1a; 挨个右击源代码的所有头文件-》属性-》项类型。改成qt头文件形式&#xff0c;如下。

961题库 北航计算机 计算机网络 附答案 简答题形式

有题目和答案&#xff0c;没有解析&#xff0c;不懂的题问大模型即可&#xff0c;无偿分享。 第1组 习题 某网络拓扑如题下图所示&#xff0c;其中 R 为路由器&#xff0c;主机 H1&#xff5e;H4 的 IP 地址配置以及 R 的各接口 IP 地址配置如图中所示。现有若干以太网交换机…

C#中使用Mapster

Mapster是一个开源的.NET对象映射库&#xff0c;它提供了一种简单而强大的方式来处理对象之间的映射。 多个映射框架的性能对比&#xff1a; 第一步安装Mapster 使用方法 public class Test {public string name { get; set; }public string sex { get; set; }public string…

光伏并网逆变器UL 1741:2021标准解析

光伏并网逆变器UL 1741:2021标准解析 不同国家的安规认证可以说是光伏逆变器走向国际市场的一张通行证&#xff0c;由于全球各国家的电网制式及并网政策的不同差异&#xff0c;这对逆变器测试顺利的通过安规测试认证 还是有一定的技术难度&#xff0c;也是中国光伏制造企业迫切…

在Linux中tomcat占用内存过高可以通过导出hprof日志来解决

自动导出hprof日志 第一种方法&#xff1a; Tomcat的hprof日志是一种用于分析Java堆内存使用情况的工具&#xff0c;它可以帮助开发人员找到内存泄漏的原因。 hprof日志可以在特定的时间点对Java堆内存进行快照&#xff0c;并生成详细的分析报告。 启用hprof日志导出的具体…

零基础写框架:从零设计一个模块化和自动服务注册框架

模块化和自动服务注册 基于 ASP.NET Core 开发的 Web 框架中&#xff0c;最著名的是 ABP&#xff0c;ABP 主要特点之一开发不同项目(程序集)时&#xff0c;在每个项目中创建一个模块类&#xff0c;程序加载每个程序集中&#xff0c;扫描出所有的模块类&#xff0c;然后通过模块…

“去员工化”这个潮流谁也挡不住,六大诱因分析。

去员工化→恐怕是未来工作的主流&#xff0c;一方面有成本的原因&#xff0c;另一方面也有技术进步、雇佣形式创新等原因&#xff0c;这个潮流有利也有弊&#xff0c;关键看我们是如何兴利除弊。 "去员工化"是指企业在运营中减少或取消传统雇佣制度&#xff0c;更多…

HOW - vscode 使用指南

目录 一、基本介绍1. 安装 VS Code2. 界面介绍3. 扩展和插件4. 设置和自定义 二、常用界面功能和快捷操作&#xff08;重点&#xff09;常用界面功能快捷操作 三、资源和支持 Visual Studio Code&#xff08;VS Code&#xff09;是一款由微软开发的免费、开源的代码编辑器&…

vue开发网站-使用插件element、vant 遇到的问题

1. js把两个字符串放进一个另字符串里&#xff0c;用逗号分隔 let string1 "Hello"; let string2 "World"; let result ${string1},${string2}; console.log(result); // 输出: Hello,World2.js将字符串转为数组 const str "Hello, world!"…

HBuilder中怎样修改每次输出的内容hello?每次修改之后再次“运行到”的时候,怎样保证每次都重新编译?

要修改每次输出的内容&#xff0c;可以在代码中找到输出的地方&#xff0c;并将内容进行修改。例如&#xff0c;在JavaScript中&#xff0c;可以使用console.log()函数输出内容。在修改内容后&#xff0c;保存代码。 为了保证每次运行都重新编译代码&#xff0c;可以按照以下步…

LeetCode 算法:接雨水c++

原题链接&#x1f517;&#xff1a;接雨水 难度&#xff1a;困难⭐️⭐️⭐️ 题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,…

使用 WM_WINDOWPOSCHANGING 跟踪窗口状态变化

在窗口位置变化过程的早期&#xff0c;系统会发送 WM_WINDOWPOSCHANGING 消息。 这个和 WM_WINDOWPOSCHANGED 消息不同&#xff0c;WM_WINDOWPOSCHANGED 消息发生在窗口位置变化之后。 一个关键的区别&#xff08;除了时间之外&#xff09;是&#xff0c;您可以通过处理 WM_WI…

OpenStreetMap部署(OSM)

参考&#xff1a;https://github.com/openstreetmap/openstreetmap-website/blob/master/DOCKER.md OpenStreeMap 部署 操作系统建议使用 Ubuntu 22 版本 安装 Docker # 更新软件包索引&#xff1a; sudo apt-get update # 允许APT使用HTTPS&#xff1a; sudo apt-get inst…

艰难求生的转型之路

起因 我个人“工作水平低&#xff0c;专业能力差。”是最核心的困难。 在坚持了快十年之后&#xff0c;博客从2015-2024。 2015201620172018201920202021202220232024 从2020年之后就已经开始全面转型之路。 所有传统赛道&#xff0c;都挤满了人&#xff0c;各种限制各种约…

【图像处理与机器视觉】灰度变化与空间滤波

基础 空间域与变换域 空间域&#xff1a;认为是图像本身&#xff0c;对于空间域的操作就是对图像中的像素直接进行修改 变换域&#xff1a;变换系数处理&#xff0c;不直接对于图像的像素进行处理 邻域 图像中某点的邻域被认为是包含该点的小区域&#xff0c;也被称为窗口 …

【Linux基础】安装redis

【Linux基础】安装redis 文章目录 【Linux基础】安装redis1、安装redis步骤2、启动redis3、redis停止 1、安装redis步骤 创建文件夹存放软件目录 [rootlocalhost ~]# mkdir /sort将Redis安装包上传到Linux到soft目录 解压安装包 cd /soft tar -xvf redis-4.0.0.tar.gz -C /usr/…

高速开箱机如何更加高效、智能化

在科技飞速发展的背景下&#xff0c;物流自动化已成为行业发展的必然趋势。其中&#xff0c;高速开箱机以其高效、精准的特性&#xff0c;在物流自动化领域大放异彩&#xff0c;成为推动行业发展的强大动力。星派将深入探讨高速开箱机在物流自动化中的应用与前景&#xff0c;展…

变现 5w+,一个被严重低估的 AI 蓝海赛道,居然用这个免费的AI绘画工具就能做!

大家好&#xff0c;我是画画的小强&#xff0c;致力于分享各类的 AI 工具&#xff0c;包括 AI 绘画工具、AI 视频工具、AI 写作工具等等。 但单纯地为了学而学&#xff0c;是没有任何意义的。 这些 AI 工具&#xff0c;学会了&#xff0c;用起来&#xff0c;才能发挥出他们的…