linux下信号机制(异步)的理解和使用

本文详细介绍了Linux下信号机制的概念,将其比喻为硬件中断的软件模拟。通过应用层的signal函数注册处理函数,配合fcntl设置O_ASYNC标志,实现与驱动层的异步通信。当文件描述符可读写时,驱动层发送SIGIO信号给应用层。文中还给出了示例代码,解释了fasync结构体和相关函数的作用。
摘要由CSDN通过智能技术生成

Linux下信号的理论概念

====================================
再其他文章中描述过关于阻塞和非阻塞,这两种方式都是属于应用程序主动去查看驱动层资源,阻塞方式:进程睡眠等待资源有效,非阻塞方式:应用程序调用poll,select,epoll等函数不断轮询的查看资源有效
信号就和以上两种就不太一样了,通俗点讲就像我们要做一件事,可以去做别的事情(不用睡眠,也不用轮询)也就是常说的异步通信.
本人是从单片机起步的,这里的信号(异步)就可以理解为硬件的中断,被软件模拟出来了.那么现在可能有点懵,虽然说是模拟中断,那么肯定有中断号吧,也肯定又中断处理函数吧,而且谁给谁产生信号,谁又如何去接收这个信号呢?
我们随着这几个疑问来说下。
再内核源码中的路径下,又一些宏定义:
linux_kernel\arch\alpha\include\uapi\asm\signal.h

// Signal numbers
#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 /* 子进程结束 */
#define SIGCONT 18 /* 进程继续 */
#define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
#define SIGTSTP 20 /* 停止进程的运行(Ctrl+Z 组合键) */
#define SIGTTIN 21 /* 后台进程需要从终端读取数据 */
#define SIGTTOU 22 /* 后台进程需要向终端写数据 */
#define SIGURG 23 /* 有"紧急"数据 */
#define SIGXCPU 24 /* 超过 CPU 资源限制 */
#define SIGXFSZ 25 /* 文件大小超额 */
#define SIGVTALRM 26 /* 虚拟时钟信号 */
#define SIGPROF 27 /* 时钟信号描述 */
#define SIGWINCH 28 /* 窗口大小改变 */
#define SIGIO 29 /* 可以进行输入/输出操作 */
#define SIGPOLL SIGIO
/* #define SIGLOS 29 */
#define SIGPWR 30 /* 断点重启 */
#define SIGSYS 31 /* 非法的系统调用 */
#define SIGUNUSED 31 /* 未使用信号 */

可以通过驱动层去发送这些中断号给应用程序,那么应用程序就会有一个处理这个中断的接口,就引入一个函数signal,可以通过man手册查看此函数信息:

#include <signal.h>//头文件
typedef void (sighandler_t)(int);//类型名
sighandler_t signal(int signum, sighandler_t handler);//函数声明
参数signum:上面的信号的number
参数handler:sighandler_t类型的函数
我们先用一下#define SIGINT 2 /
终端中断(Ctrl+C 组合键) */
这个SIGINT功能,再终端我们摁组合键Ctrl+C打印出来此信号number,并且打印字符串“signal interrupt”.

// 应用层信号的使用例子
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
void signal_handle(int num)
{
   
	printf("signal interrupt %d\n",num);
	exit(0);
}
void main(void)
{
   
	signal(SIGINT,signal_handle);
	while(1);
}

打印结果应该是signal interrupt 2,因为SIGINT宏定义是2,结果也是如此.
在这里插入图片描述
以上就是在应用层的使用,下面就说一下,我们应用层在调用signa函数后,驱动层如何处理的.
首先需要了解的是应用程序是通过
int fcntl(int fd, int cmd, … /* arg */ );
来和驱动建立联系的。
其次这里需要说明第二个参数cmd,函数是否有第三个参数,是根据cmd来决定的,具体我们来看一下man手册对fcntl描述就可以知道.

摘自man手册对fcntl描述一段话
fcntl() can take an optional third argument. Whether or not this argument is required is determined by cmd. The required argument
type is indicated in parentheses after each cmd name (in most cases, the required type is int, and we identify the argument using the
name arg), or void is specified if the argument is not required.

在man手册中fcntl描述中找到Managing signals段落:

摘自man手册对Managing signals描述一段话
If you set the O_ASYNC status flag on a file descriptor by using the F_SETFL command of fcntl(), a SIGIO signal is sent whenever
input or output becomes possible on that file descriptor. F_SETSIG can be used to obtain delivery of a signal other than SIGIO.
If this permission check fails, then the signal is silently discarded.

从这句话了解到
第一:需要设置文件描述符的状态标志为O_ASYNC。
第二:F_SETFL是一个命令,也就是参数cmd,使用这个命令设置。
第三:既然F_SETFL是cmd,那么是否有第三个参数就要看这个F_SETFL是否带参数需求。
第四:当文件描述符设置O_ASYNC这个标志后,该文件描述符描述的文件会在可以对其读写操作的时候将SIGIO 信号会被发送出来(驱动层操作).现在是在应用层角度来说,我们这样就是和驱动层建立了连接了,我们只需要像文章开头那样进行一个信号处理的注册就可以了。

那么我们就从第一个来解决,设置标志需要调用F_SETFL命令,那么就到了第二个问题,还是看手册:

摘自man手册中对F_SETFL描述一段话
F_SETFL (int)
Set the file status flags to the value specified by arg. File access mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation flags (i.e., O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are ignored. On Linux this command can change only the O_APPEND, O_ASYNC,
O_DIRECT, O_NOATIME, and O_NONBLOCK flags. It is not possible to change the O_DSYNC and O_SYNC flags; see BUGS, below.

看这个就一目了然了,fcntl是否有参数确实是根据cmd来说的,这个F_SETFL 就有一个参数,int类型,所以我们只要这样调用fcntl(fd,F_SETFL,O_ASYNC);
这里要说一下,笔者这里第三个参数这么写没毛病,但是用的时候需要注意一下,这样写就把该fd之前的标志都覆盖了,所以应该先得到该文件描述符之前的old flag,在第三个参数中old flag或上这个O_ASYNC是正确写法。
最后的代码会有demo,供参考.

下面真正进到驱动层,前面说的fcntl函数对应驱动中中操作集结构体的
**int (fasync) (int, struct file , int);
以下是通过网上查找得到的,具体想知道为什么智能读源码的层层调用了,不过意义不大,所以我认为就记录一下,内核中别的文件也是这么用的。
关联的数据结构:
fasync_struct结构体
关联的函数:
1)申请初始化异步结构体或者释放异步结构体的函数(申请还是释放参数mode决定)
int fasync_helper(int fd, struct file *filp, int mode ,struct fasync_struct **fa);
2) 释放信号用的函数void kill_fasync(struct fasync_struct **fa, int sig, int band);
引用内核源码文件夹内的部分代码来看看吧.

// 摘自linux_kernel\include\linux\pipe_fs_i.h
struct pipe_inode_info {
   
	struct mutex mutex;
	wait_queue_head_t wait;
	unsigned int nrbufs, curbuf, buffers;
	unsigned int readers;
	unsigned int writers;
	unsigned int files;
	unsigned int waiting_writers;
	unsigned int r_counter;
	unsigned int w_counter;
	struct page *tmp_page;
	struct fasync_struct *fasync_readers;
	struct fasync_struct *fasync_writers;
	struct pipe_buffer *bufs;
};
// 摘自linux_kernel\fs\pipe.c
const struct file_operations pipefifo_fops = {
   
	.open		= fifo_open,
	.llseek		= no_llseek,
	.read_iter	= pipe_read,
	.write_iter	= pipe_write,
	.poll		= pipe_poll,
	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值