Linux基础(08)信号通信机制
1.Linux中的信号(有32个) 信号会中断一些函数的阻塞
https://zhidao.baidu.com/question/1766690354480323100.html
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPOLL SIGIO
/*
#define SIGLOST 29
*/
#define SIGPWR 30
#define SIGSYS 31
/* signal 31 is no longer "unused", but the SIGUNUSED macro remains for backwards compatibility */
#define SIGUNUSED 31 /* These should not be considered constants from userland. */
#define SIGRTMIN 32
#define SIGRTMAX _NSIG
SIGCHLD的更多细节 子进程状态变更了,例如停止、继续、退出等,都会发送这个信号通知父进程。而父进程就能通过 wait/waitpid 来获悉这些状态了 https://segmentfault.com/a/1190000015060304
信号因为是异步机制(可能会信号叠加)会造成系统的不稳定,所以应尽量减少信号(应用于一些突发事件)
如:Linux终端的Ctrl+C(强制结束进程)这样的信号.
2.如何发起异步操作
2.1(kill -signum pid) kill其实是发送信号的宏观指令 如kill -9(SIGKILL
) pid
signal()把信号绑定指定函数
signal的大概使用: signal(signum(SIGINT) , myfunc) , 当发出SIGINT信号时执行myfunc函数 https://www.runoob.com/cprogramming/c-function-signal.html
int kill(pid,signum);
pid>0 发给pid进程
pid=0 发给当前进程组的所有进程
pid=-1 发送给所有进程
pid<0 发送给|PID|所对应的组上
pid = getpid()获得当前进程的ID gpid = getgpid():获得当前进程的组ID
2.2自举信号
自己给自己发送信号 , 也就是给程序本身 raise 一个信号,然后执行信号所绑定的函数
int raise(int sig); == int kill(getpid , signum)
2.3定时函数
usigned int alarm(unsigned int seconds) 函数会在指定seconds之后收到SIGALRM信号
如: 两秒后打印一个printf signal(SIGALRM , printf);
alarm(2);
alarm是一次性的 大概实现是在内核的一个 jiffies(程序开始时从0每隔10毫秒+1的一个数) 上累加的,如一个程序开始执行1秒后触发, 那就是 jiffies+100 触发. 因为一个程序只有一个jiffise,所以多次使用alarm时后面的alarm会覆盖前面的
ualarm是多次的 useconds_t ualarm(useconds_t usecs , useconds_t interval);
第一个参数是第一次产生的时间 , 第二个参数是间隔时间
因为alarm和ualarm(不推荐使用)时间不准确 所以有了setitimer(推荐使用) 精确的定时器
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval new_value, struct itimerval *old_value)
详细 https://blog.csdn.net/fjt19900921/article/details/8072903
struct itimerval {
struct timeval it_interval; /* next value 间隔时间*/
struct timeval it_value; /* current value 第一次的时间*/
}; struct timeval {
time_t tv_sec; /* seconds 秒*/
suseconds_t tv_usec; /* microseconds 微秒*/
};
Linux会提供三个定时器,每个定时器返回的信号都是不同的 ITIMER_REAL ITIMER_VIRTUAL ITIMER_PROF ,一般没什么区别,使用第一个就行了
3.安装和捕获信号
3.1忽略信号
signal(signum , SIG_IGN); SIG_IGN = 接收到信号,但是作空处理和屏蔽信号(让信号在屏蔽队列等待,何时关闭屏蔽,何时才接收信号)有区别
3.2自定义捕捉函数
signal(signum , handler);
SIGKILL 和 SIGSTOP 不能被捕捉
3.3系统默认信号函数
如 ctrl+c 会发送SIGINT 执行系统的函数结束进程
3.4因为signal()局限小不推荐使用 , 所以有了sigaction对象
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact); struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
//上面两个选其一使用 ,选哪个取决于flag的状态
sigset_t sa_mask; //一般信号屏蔽时用的(把你想屏蔽的信号加入到sa_mask集合, 再利用信号屏蔽函数进行操作 看3.5)
int sa_flags; //会影响信号接受特殊标志
void (*sa_restorer)(void); //不做使用NULL.有必要时传入个sigaction对象,用作备份方便恢复,sigaction()会把旧的sigaction对象和所绑定的设置传出
};
sa_flags = 0 调用第一个函数
sa_flags = SA_INTERRUPT:由此信号中断的系统调用不会自动重启动(针对sigaction的XSI默认处理方式不会自动重启) SA_RESTART: 由此信号中断的系统调用会自动重启。 SA_NOCLDSTOP:一般当进程终止或停止时都会产生SIGCHLD信号,但若对SIGCHLD信号设置了该标志,当子进程停止时不产生此信号。当子进程终止时才产生此信号。
SA_NODEFER:
SA_RESETHAND:
这两个标志对应早期的不可靠信号机制,除非明确要求使用早期的不可靠信号,否则不应该使用。这里也不多做介绍
SA_SIGINFO:当我们没有使用 SA_SIGINFO 标志时,调用的是 sa_handler指定的信号处理函数
我们使用了 SA_SIGINFO标志 然后使用了 sa_sigaction字段表示的处理函数。
sigset_t 信号集合 siginfo_t {
int si_signo; /* Signal number */ //信号编号
int si_errno; /* An errno value */ //如果为非0值则错误代码于之关联
int si_code; /* Signal code */ //说明进程如何接收信号以及从何处收到
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */ //发送信号的进程ID
uid_t si_uid; /* Real user ID of sending process */ //发送信号的用户ID
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in
glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address
(since Linux 2.6.32) */
}
3.5信号屏蔽集合的操作函数
int sigemptyset(sigset_t* set); 清空信号集合
int sigfillset(sigset_t* set); 填满信号集合(把所有信号都放进去)
int sigaddset(sigset_t* set, int signum); 往set集合里追加signum
int sigdelset(sigset_t* set, int signum); 把signum从set中删除
int sigismember(const sigset_t *set, int signum); 测试set里是否有signum
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void myHandler(int sig);
int main(int argc,char *argv[])
{
struct sigaction act, oact; act.sa_handler = myHandler;
sigemptyset(&act.sa_mask); /*initial. to empty mask*/
act.sa_flags = ;
sigaction(SIGUSR1, &act, &oact);
while ()
{
printf("Hello world.\n");
pause();
}
} void myHandler(int sig)
{
printf("I got signal: %d.\n", sig);
}
4.sigqueue() 发送信号函数 https://www.cnblogs.com/mickole/p/3191804.html
之前学过kill,raise,alarm,abort等功能稍简单的信号发送函数,现在我们学习一种新的功能比较强大的信号发送函数sigqueue.
sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。
int sigqueue(pid_t pid, int sig, const union sigval val) 调用成功返回 0;否则,返回 -1
sigqueue的第一个参数是指定接收信号的进程ID,
第二个参数确定即将发送的信号,
第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
typedef union sigval { //可以存放4字节的数据 指针只能在同一进程传递 https://www.cnblogs.com/mickole/p/3191804.html
int sival_int;
void *sival_ptr;
}sigval_t;
si_value :系统调用sigqueue发送信号时,sigqueue的第三个参数就是sigval联合数据结构,当调用sigqueue时,该数据结构中的数据就将拷贝到信号处理函数( void (*sa_sigaction)(int, siginfo_t *, void *))的第二个参数中。这样,在发送信号同时,就可以让信号传递一些附加信息。信号可以传递信息对程序开发是非常有意义的。
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。
收发信号的具体实现:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<string.h>
//#include<sys/siginfo.h> void handler(int,siginfo_t *,void *); int main(void)
{
struct sigaction act;
act.sa_sigaction=handler;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
if(sigaction(SIGINT,&act,NULL)<)
{
printf("error");
exit();
}
for(;;)
pause();
return ;
} void handler(int sig,siginfo_t * info,void *ctx)
{
printf("recv a sid=%d data=%d data=%d\n",sig,info->si_value.sival_int,info->si_int);
}
sigvalue_recv
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<string.h> int main(int argc,char *argv[])
{
if(argc!=)
{
printf("arguments error!");
exit();
} pid_t pid=atoi(argv[]);//将进程号转化为整数
union sigval v;
v.sival_int=;
//这儿只是利用SIGINT来发送数据,
//任何信号都行,只需要发送端与接收端规定一致即可
sigqueue(pid,SIGINT,v);
return ;
}
sigvalue_send
5.设置信号屏蔽集
利用信号屏蔽集合的操作函数,设置好信号屏蔽集, 再使用sigprocmask()进行添加或删除
int sigprocmask(int how, const sigset_t set, sigset_t *oldset);
how = SIG_BLOCK 将设置好的信号屏蔽集set加入到当前进程的屏蔽集里
SIG_UNBLOCK 将设置好的信号屏蔽集set从当前进程的屏蔽集里删除
SIG_SETMASK 将设置好的信号屏蔽集set设置为当前进程的屏蔽集
第三个参数作备份用的
6.未决信号 因为未捕获或屏蔽的信号称为未决信号
屏蔽信号不是不接收该信号了,而是接收信号后把该信号放入信号屏蔽的队列里不做处理, 这些信号称为未决信号 , 队列里的信号不会重复,因为信号会被覆盖.
忽略的信号不会加入到信号屏蔽的队列里
int sigpending(sigset_t *set); 查询有多少未决信号
7.阻塞的信号函数
int sigsuspend(const sigset_t *mask); 阻塞进程(收到mask里的信号继续阻塞),等待 mask 之外的信号唤醒
阻塞信号的实现 https://blog.csdn.net/weiyuefei/article/details/72457739
屏蔽信号会收到信号但是不做处理,他会收到未决信号队列里, 而阻塞信号会收到也会处理,但是不会返回
8.信号通信
信号可以中断函数的阻塞 , 如: accept被信号打断后的处理
ACCEPT:
confd = accept(srvfd , (struct sockaddr *)&tcilent , &tclientaddlen)
if(- == confd)
{
if(errno == EINTR)
{
goto ACCEPT;
}
else
{
return -;
}
}
因为信号函数不能做时间长的(信号处理函数时间过长可能会出现被其他信号中断的情况) , 容易死锁的 , 线程不安全的操作 ,
所以有了把信号转变成事件的操作: 信号处理函数通过管道pipe(pfd) , wirte(pfd[0] , signum/void* ptr , size),往管道写入数据 ,
管道的另一端会往 fd 写入数据, 然后发生event , 这样就可以把信号转换成事件了, 再通过epoll处理, 高并发服务器一般都是这样写的
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<sys/time.h>
#include<error.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include <fcntl.h>
#include<errno.h> #if 1
/*****************************************************************************
函 数 名 : MyHandler
功能描述 : 信号处理函数
输入参数 : int sig , struct siginfo_t *info , void *ctx
输出参数 : 无
返 回 值 : void
调用函数 :
被调函数 : 修改历史 :
1.日 期 : 2019年11月8日 星期五
作 者 : ljf
修改内容 : 新生成函数 *****************************************************************************/
void MyHandler( int sig , struct siginfo_t *info , int *pipefd)
{
char *buff = "hello world\n";
size_t len = sizeof(buff);
write(pipefd[], buff, len);
} /*****************************************************************************
函 数 名 : readn
功能描述 : 不被信号所打断的完整读取
输入参数 : int fd , void* buf , size_t len
输出参数 : 无
返 回 值 : ssize_t
调用函数 :
被调函数 : 修改历史 :
1.日 期 : 2019年11月8日 星期五
作 者 : ljf
修改内容 : 新生成函数 *****************************************************************************/
ssize_t readn(int fd , void* buf , size_t len)
{
size_t nleft = len;
ssize_t nread;
char* pbuf = buf;
while ( nleft > )
{
if ( (nread = read(fd , pbuf , nleft)) == - )
{
if(errno == EINTR) //如果被信号所中断
nread = ; //continue 因为nread=0 所以等同于重新开始下一次循环
else
return -;
}else if ( nread == )
{
break;
}
nleft -= nread; //实际大小减去已读取的大小 等于未读取的大小
pbuf += nread; //从未完的位置开始继续读取
}
return (len - nleft);
} int main(int argc, char *argv[])
{
struct sigaction act , oact;
int pipe_fd[] = {,};
int epoll_fd; pipe(pipe_fd); act.sa_handler = MyHandler; //捕捉信号和处理
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
if ( sigaction(SIGUSR1 , &act , pipe_fd) < )
{
perror("sigaction");
exit();
} epoll_fd = epoll_create();
if ( epoll_fd < )
{
perror("epoll_create1");
exit();
}
struct epoll_event ep_ev;
ep_ev.events = EPOLLIN;
ep_ev.data.fd = pipe_fd[];
if ( epoll_ctl(epoll_fd , EPOLL_CTL_ADD , pipe_fd[] , &ep_ev) < )
{
perror("epoll_ctl");
exit();
} struct epoll_event ready_event[]; //存放就绪事件
int maxnum = ; //就绪事件最大数
int timeout = ; //超时时间10s
int ret = ; //接收就绪事件的数量
while()
{
ep_wait:
switch ( ret = epoll_wait(epoll_fd , ready_event , maxnum , timeout) )
{ case - :
{
if ( errno == EINTR )
{
goto ep_wait;
}
else
{
perror("epoll_wait");
exit();
}
}break; case :
fputs("time out...\n", stdout);
break;
default:
{
int i=;
char buf[];
for ( ; i< ret ; i++ )
{
if ( ready_event[i].events & EPOLLIN )
{
memset(buf,'\0',sizeof(buf));
size_t r_s = readn(pipe_fd[] , buf ,);
fputs(&buf , stdout);
ep_ev.data.fd = ready_event[i].data.fd;
ep_ev.events = EPOLLOUT | EPOLLET;
epoll_ctl(epoll_fd , EPOLL_CTL_MOD , ready_event[i].data.fd , &ep_ev);
}
if ( ready_event[i].events & EPOLLOUT )
{
fputs("OK\n", stdout);
}
} }break;
} } return ;
}
#endif /* #if signal to event */
总结: 信号是内核发送给某一进程的一种消息 ,信号机制是Linux系统中用于进程之间相互通信或操作的一种机制
1. Linux内核的信号都有一个默认绑定的执行函数 , 可以使用signal(num,func)重定向信号对函数的绑定 ,SIGKILL和SIGSTOP不能被捕捉和重定向
kill(pid,signum) 指定进程发送信号 , raise(signum)当前进程自举信号 , signal(num,SIG_IGN)SIG_IGN忽略信号(做空处理),和屏蔽信号不同
2. alarm(seconds)定时函数等待指定时间后发起SIGALRM信号 , 一个进程只能有一个alarm ,因为它是从程序开启时开始计时的,比如设置5,秒, 那么是程序开始5秒后触发ualarm(second, interval)多次触发定时器, second是第一次触发的时间 ,interval是间隔的触发时间 , 多个alarm,后面的会覆盖前面的
3.因为alarm和ualarm时间都不准确 ,所有有了settimer(which,itimerval *new_value, itimerval *old_value) which定时器类型有三种一般都使用第一种ITIMER_REAL , new_value代表定时器第一次触发和间隔触发的时间 , old_value备份上次时间一般用不上设置NULL
4.signal局限有点小所以有了sigaction, 创建sigaction对象并分配(指定信号函数,指定屏蔽信号集), 再使用sigaction()让信号函数和信号绑定
5.sigqueue()可以给指定进程 ,带参的发送信号 , 配合sigaction()使用,sigaction使用SA_SIGINFO标志来接收带参信号中的参数
Linux基础(08)信号通信机制的更多相关文章
- 【深入浅出Linux网络编程】 “基础 -- 事件触发机制”
回顾一下“"开篇 -- 知其然,知其所以然"”中的两段代码,第一段虽然只使用1个线程但却也只能处理一个socket,第二段虽然能处理成百上千个socket但却需要创建同等数量的线程 ...
- 20155301 滕树晨linux基础——linux进程间通信(IPC)机制总结
20155301 滕树晨linux基础--linux进程间通信(IPC)机制总结 共享内存 共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在 ...
- Linux基础 - 系统优化及常用命令
目录 Linux基础系统优化及常用命令 Linux基础系统优化 网卡配置文件详解 ifup,ifdown命令 ifconfig命令 ifup,ifdown命令 ip命令 用户管理与文件权限篇 创建普通 ...
- Linux基础系统优化及常用命令
# Linux基础系统优化及常用命令 [TOC] ## Linux基础系统优化 Linux的网络功能相当强悍,一时之间我们无法了解所有的网络命令,在配置服务器基础环境时,先了解下网络参数设定命令. - ...
- OracleOCP认证 之 Linux基础
Linux 基础 一.SHELL 1: Shell 简介 shell 是用户和Linux 操作系统之间的接口.Linux 中有多种shell, 其中缺省使用的是bash. Linux 系统的shell ...
- day55 linux 基础以及系统优化
Linux系统基础优化及常用命令 Linux基础系统优化 引言没有,只有一张图. Linux的网络功能相当强悍,一时之间我们无法了解所有的网络命令,在配置服务器基础环境时,先了解下网络参数设定命令 ...
- Linux基础入门之网络属性配置
Linux基础入门之网络属性配置 摘要 Linux网络属性配置,最根本的就是ip和子网掩码(netmask),子网掩码是用来让本地主机来判断通信目标是否是本地网络内主机的,从而采取不同的通信机制. L ...
- 还是不想改报告,伊阿忆啊哟-Linux基础继续
hi 虽然今天是最最美好的周六(前不着工作日后不着工作日),但老子还要来改报告,但额就是不想改,你拿我有啥办法啊... 争取完结Linux基础 一.Linux常用命令(三) 4.帮助命令 4.1 帮助 ...
- 买错的电影票,含着泪也得看-LAMP搭建&Linux基础
hi 没说过,上周五室友过生请客,在龙湖里吃嗨了喝爽了,回去的路上侃侃而谈.说好的这周一起去看年内最后的大片,火星救援的,谁知道老子眼神不好,买错了电影的时间...把周六的约定提前到了今儿个下午,ma ...
随机推荐
- ent 基本使用五 schema介绍
ent 提供了自动生成schema 但是,我们可以基于生成schema 进行扩展,schema 主要包含以下配置 实体的字段(或者属性)比如 user 的name 以及age 实体的边(关系),比如u ...
- 9-网页,网站,微信公众号基础入门(使用PHP实现微信token验证)
https://www.cnblogs.com/yangfengwu/p/11062422.html 这一节看怎么用PHP实现上一节的功能 关掉上一节的 学了这么久,忘了告诉大家怎么关闭程序了.... ...
- C语言中常见的图形打印总结
直角三角形(靠右直立) 示例实现代码如下: int main(){ int n; int i,j; cin >> n; if(n<= 0){ cout << " ...
- NOI2019 Day2游记
开场T1是个最短路优化建图,边向二维矩形内所有点连,本来可以写树套树的,但是卡空间(128MB),后来发现其实是不用把边都建出来的,只需要用数据结构模拟dijkstra的过程,支持二维区间对一个值取m ...
- DNN的BP算法Python简单实现
BP算法是神经网络的基础,也是最重要的部分.由于误差反向传播的过程中,可能会出现梯度消失或者爆炸,所以需要调整损失函数.在LSTM中,通过sigmoid来实现三个门来解决记忆问题,用tensorflo ...
- utility.py:61: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array in
utility.py:: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; ...
- 自顶向下深入分析Netty(七)--ChannelPipeline和ChannelHandler总述
自顶向下深入分析Netty(七)--ChannelPipeline和ChannelHandler总述 自顶向下深入分析Netty(七)--ChannelPipeline源码实现 自顶向下深入分析Net ...
- Java里方法的参数传递方式
Java里方法的参数传递方式只有一种:值传递. Java中参数传递的都是参数值 下面从两个维度来看 1.传递的参数是8种基本数据类型 这个比较好理解,8种基本数据类型,作为参数时,可以理解为原来的一个 ...
- sql server数据库备份单个表的结构和数据生成脚本【转】
1.使用场景:sql server数据库备份单个表的结构和数据,在我们要修改正式系统的数据的一天或者多条某些数据时候,要执行update语句操作,安全稳健考虑,最好先做好所修改的表的结构和数据备份! ...
- android gradle使用阿里源
使用阿里源 新建一个init.gradle 文件到$USER_HOME/.gradle/目录下,这们就省的翻墙了. init.gradle 文件内容如下: allprojects { reposito ...