unix/linux信号:2.API使用

1. 基本数据结构

信号: int sig;
信号集:sigset_t set;

  • sigset_t类型有的是数组, 有的是unsigned long类型, 原理是每个信号在信号集里是一个位掩码。
// 数组
typedef struct {
  unsigned long sig[_NSIG_WORDS]} sigset_t
// unsigned long
typedef __sigset_t sigset_t;
typedef unsigned long __sigset_t;

2. 常见信号

SIGINT: ctrl + c 绑定,结束前台进程
SIGQUIT: ctrl + \ 绑定,结束进程,产生core
SIGTERM: kill 或者killall命令,可以被阻塞和处理, kill不加参数默认产生的信号
SIGKILL: kill -9 pid 或者 kill -SIGKILL不能被应用程序捕获,也不能被阻塞或忽略
SIGHUP: 挂起信号,在关闭终端是产生,默认处理是终止收到给信号的进程。
SIGSEGV: 默认动作是异常终止。这个动作也许会结束进程,但是可能生成一个核心文件以帮助调试。
SIGPIPE: 当服务器 close 一个连接时,若 client 继续向服务器发数据,根据 TCP 协议的规定,客户端会收到一个 RST 响应,client再往这个服务器发送数据时,系统会发出一个 SIGPIPE 信号给客户端进程,导致客户端进程退出。
SIGALRM: 调用alarm函数触发,可阻塞
SIGUSR1: 调用kill -USR1 pid触发

3. 信号API

3.1 signal: 绑定信号处理函数
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void signalHandle(int sig)
{
	if (sig == SIGQUIT) {
		printf("sigHand SIGQUIT\n");
	} else if (sig == SIGINT) {
		printf("sigHand SIGINT\n");
	} else if (sig == SIGTERM) {
		printf("sigHand SIGTERM");
	} else if (sig == SIGUSR1) {
		printf("sigHandle SIGUSR1");
	} else {
		printf("other signal\n");
	}
}

int main()
{
	signal(SIGINT, signalHandle);
	signal(SIGQUIT, signalHandle);
	signal(SIGTERM, signalHandle);
	signal(SIGUSR1, signalHandle);
	
	for (int i = 0; ;i++) {
		printf("%d\n", i);
		sleep(3);
	}
	return 0;
}
输入:
kill -USR1 58912
kill -SIGINT 58912
kill -SIGTERm 58912
kill -sigkill 58912

SIG_DFL:默认处理函数
SIG_IGN:忽略捕获消息

3.2 kill

int kill(pid_t pid, int signo);
1)当pid>0时,给指定进程发信号
2)当pid == 0时,给自己和自己同组的进程组发信号
3)当pid == -1时,给每个进程,除了0和1(init)进程发信号。
4)当pid < 0pid != -1时,给-pid进程组发信号
signo0时,表示给进程发空信号,测试给进程是否还存在。如果不存在,会返回ESRCH错误

// 给指定的进程发信号
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
	int pid;
	while(1) {
		scanf("%d", &pid);
		printf("%d\n", pid);
		kill(pid, SIGINT);
		kill(pid, SIGQUIT);
		kill(pid, SIGTERM);
		kill(pid, SIGUSR1);
	}
	return 0;
}
// 给自身同组的进程组发信号
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void signalHandle(int sig)
{
	if (sig == SIGINT) {
		printf("sigHand SIGINT\n");
	} else {
		printf("other signal\n");
	}
}

int main()
{
	signal(SIGINT, signalHandle);
	signal(SIGUSR1, signalHandle);
	
	for (int i = 0; ;i++){
		printf("%d\n", i);
		if (i % 2) {
			kill(0, SIGINT);
		}
		sleep(3);
	}
	return 0;
}
sigqueue
raise
3.3 raise:

int raise(int signo);
给自身发信号,相当于kill(getpid(), signo)

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void signalHandle(int sig)
{
	if (sig == SIGINT) {
		printf("sigHand SIGINT\n");
	} else {
		printf("other signal\n");
	}
}

int main()
{
	signal(SIGINT, signalHandle);
	signal(SIGUSR1, signalHandle);
	
	for (int i = 0; ;i++){
		printf("%d\n", i);
		if (i % 2) {
			raise(SIGINT);
		}
		sleep(3);
	}
	return 0;
}
3.4 sigprocmask 信号掩码

内核为每个进程维护一个信号掩码,即一组信号,并阻塞其针对该进程的传递。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

how取值:
SIG_BLOCK:将当前信号集合set取并集
SIG_UNBLOCK:将set从当前信号集中移除
SIG_SETMASK:将set置换当前信号集
如果set为空,则返回当前信号集,不管how的取值。

1)进程在执行某个信号的处理函数时,同一信号再次进来会造成函数的重入或者其他信号处理函数被执行,从而打断当前信号处理函数的执行。为了不被打断,通过sigprocmask设置进程信号屏蔽集。

2)对于一个被屏蔽的信号,即便目标进程将它的处理方式设置为了SIG_IGN,内核也不会忽略并丢弃这个信号。这是因为啊,在这个信号被屏蔽的这段时间里,进程是有可能将SIG_IGN改成其他的处理函数的。进程可是以它解除对这个信号的屏蔽时,设置的处理函数为依据的,在信号阻塞期间的改动通通不算数。要是内核按照它之前的设定把信号忽略了,它反倒是要怪罪内核了,内核可不想背这个锅。

信号集操作

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(sigset_t *set);
// 打印被信号集
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int printSigset(sigset_t &sigSet) 
{
	int cnt = 0;
	printf("printf:begin\n");
	for (int i = 0; i < NSIG; i++) {
		if (sigismember(&sigSet, i)) {
			printf("%d-%s\n", i, strsignal(i));
			cnt++;
		}
	}
	printf("cnt = %d, printf end\n", cnt);
}

int main() {
sigset_t old;
sigset_t old2;
sigset_t blockSet;
sigaddset(&blockSet, SIGINT);
sigaddset(&blockSet, SIGQUIT);
sigaddset(&blockSet, SIGTERM);
sigprocmask(SIG_BLOCK, &blockSet, &old); // old为原始信号集
printSigset(old);
sigprocmask(SIG_BLOCK, NULL, &old2); // old2为add后的信号集
printSigset(old2);
sigprocmask(SIG_SETMASK, &blockSet, &old); // 将老的信号集置回去
return 0;
}
3.5 sigpending 处于等待的信号

int sigpending(sigset_t *set);
1)当内核传递给进程被阻塞的信号时,信号会添加到进程的等待队列中,当进程对阻塞信号释放时,再将等待信号传递给进程。
2)标准信号1-31进入等待信号集时,如果同一标准信号有多次进入,最终只会向进程传递一次该标准信号。同一实时信号如果多次进入,则会排成一个队列,待进程阻塞释放时,再将该实时信号的队列逐一传递给进程。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
int printSigset(sigset_t &sigSet) 
{
	int cnt = 0;
	printf("printf:begin\n");
	for (int i = 0; i < NSIG; i++) {
		if (sigismember(&sigSet, i)) {
			printf("%d-%s\n", i, strsignal(i));
			cnt++;
		}
	}
	printf("cnt = %d, printf end\n", cnt);
}

int main()
{
	sigset_t oldSet, newSet, pendSet;
	sigaddset(&newSet, SIGINT);
	sigaddset(&newSet, SIGQUIT);
	sigprocmask(SIG_BLOCK, &newSet, &oldSet);
	printSigset(oldSet);
	printf("=====1\n");
	sigpending(&pendSet);
	printSigset(pendSet);
	printf("=====2\n");
	//raise(SIGQUIT);
	//raise(SIGINT);
	kill(getpid(), SIGINT);
	kill(getpid(), SIGQUIT);
	sleep(5);
	sigpending(&pendSet);
	printSigset(pendSet);
	printf("=====3\n");
	if (sigprocmask(SIG_SETMASK, &oldSet, NULL) == 0) { // 这里为啥coredump???
		printf("sigprocmask error2 \n");
		printSigset(newSet);
	} else {
		printf("sigprocmask error \n");
	}
	printf("sigprocmask \n");
	return 0;
}
3.6 sigaction 同signal,改变信号的处理函数

int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact);

简化的结构示意
struct sigaction {
void (*sa_handler) (int); // 信号处理函数,也可以是SIG_IGN,SIG_DFL 
// void (*_sa_sigaction)(int,struct siginfo *, void *); 第二个参数siginfo接收sigqueue发送的额外信息
sigset_t sa_mask; // 一组阻塞信号,在调用sa_handler是阻塞sa_mask信号集
int sa_flags; // 控制信号处理过程的各种选项,可取值SA_NOCLDSTOP,SA_NOCLDWAIT,SA_RESETHAND,SA_RESTART, SA_SIGINFO等
void (*sa_restorer)(void);
}

1)相比signal更灵活,可移植性更好。


#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

int printSigset(sigset_t &sigSet) 
{
	int cnt = 0;
	printf("printf:begin\n");
	for (int i = 0; i < NSIG; i++) {
		if (sigismember(&sigSet, i)) {
			printf("%d-%s\n", i, strsignal(i));
			cnt++;
		}
	}
	printf("cnt = %d, printf end\n", cnt);
}

void signalHandle(int sig)
{
	if (sig == SIGINT) {
		printf("sigHand SIGINT\n");
	} else {
		printf("other signal\n");
	}
}

int main()
{
	struct sigaction new_act, old_act;
	new_act.sa_handler = signalHandle;
	new_act.sa_flags = 0;
	sigemptyset(&new_act.sa_mask);
	sigaction(SIGINT, &new_act, &old_act);
	raise(SIGINT);
	return 0;
}

2)和signal的差别
1、signal在调用handler之前先把信号的handler指针恢复;sigaction调用之后不会恢复handler指针, 直到再次调用sigaction修改handler指针。这样,signal就会丢失信号,而且不能处理重复的信号, 而sigaction就可以。 因为signal在得到信号和调用handler之间有个时间把handler恢复了,这样再次接收到此信号就会执行默认的handler。(虽然有些调用,在handler的以开头再次置handler,这样只能保证丢信号的概率降低,但是不能保证所有的信号都能正确处理)

2、signal在调用过程不支持信号block;sigaction调用后在handler调用之前会把屏蔽信号(屏蔽信号中自动默认包含传送的该信号)加入信号中,handler调用后会自动恢复信号到原先的值。 signal处理过程中就不能提供阻塞某些信号的功能,sigaction就可以阻塞指定的信号和本身处理的信号,直到handler处理结束。这样就可以阻塞本身处理的信号,到handler结束就可以再次接受重复的信号。

3.7 sigqueue 同kill,raise,alarm,abort给进程传递一个信号,相比前面的几种更复杂

int sigqueue(pid_t pid, int sig, const union sigval val)

typedef union sigval {
	int sival_int;
	void *sival_ptr;
} sigval_t;

1)sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。
2)在调用sigqueue时,sigval_t指定的信息会拷贝到对应sig 注册的3参数信号处理函数的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

void sighanlder(int signo, siginfo_t *info, void *ctx)
{
	printf(" info->si_int = %d\n",info->si_int);
	printf(" info->si_value.sival_int = %d\n",info->si_value.sival_int);
}

int main()
{
	struct sigaction act;
	act.sa_sigaction = sighanlder;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_SIGINFO;
	if (sigaction(SIGINT, &act, NULL) == -1) {
		printf("sigaction error \n");
		return 0;
	}

	union sigval val;
	val.sival_int = 22;
	if (sigqueue(getpid(), SIGINT, val) == -1) {
		printf("sigqueue error\n");
	return 0;
	}

	printf("end\n");
	return 0;
}
3.8 pause

int pause(void); 终止进程,直到信号到达

3.9 sigsuspend

当前sigprocmask阻塞了信号A,调用sigsuspend将阻塞一个新的信号B或者不阻塞任何信号,程序阻塞在sigsuspend调用点,直到有非B的信号或任何信号到达,这时sigsuspend函数返回,同时把程序的阻塞恢复到信号A。 主要应对的场景是,要监听某个信号,在信号到后做处理的同时把信号给阻塞住,避免两次信号到来的时候,只处理第一次,忽略第二次,sigsuspend在处理第一次的时候会阻塞信号导致第二次信号被阻塞,相当于放到待处理队列中而不是忽略掉。
int sigsuspend(const sigset_t *mask); // always return -1

3.10 sigwait
  • 在多线程代码中,总是使用sigwait或者sigwaitinfo或者sigtimedwait等函数来处理信号。而不是signal或者sigaction等函数。因为在一个线程中调用signal或者sigaction等函数会改变所有线程中的信号处理函数。而不是仅仅改变调用signal/sigaction的那个线程的信号处理函数。
  • sigwait(&set, signo)监听信号集set中所包含的信号,并将其存在signo中。
  • sigwait函数所监听的信号在之前必须被阻塞(pthread_sigmask),调用sigwait后线程阻塞(这个时候线程信号没有被阻塞,线程被sigwait阻塞,sigwait解阻塞的条件时被监听的信号集中某个信号到来),直到有一个被监听的信号到来时,这时sigwait返回,参数signo被置为到来的信号值,同时被监听的集合再次被阻塞(pthread_sigmask作用)。
int sigwait(const sigset_t *set, int *sig);

#include<stdio.h>
#include<pthread.h>
#include<signal.h>
 
static void sig_alrm(int signo);
static void sig_init(int signo);
int
main()
{
    sigset_t set;
    int sig;
    sigemptyset(&set);
    sigaddset(&set, SIGALRM);
    pthread_sigmask(SIG_SETMASK, &set, NULL);//阻塞SIGALRM信号
     
    signal(SIGALRM, sig_alrm);
    signal(SIGINT, sig_init);
    sigwait(&set, &sig);//sigwait只是从未决队列中删除该信号,并不改变信号掩码。也就是,当sigwait函数返回,它监听的信号依旧被阻塞。
    switch(sig){
    case 14:
        printf("sigwait, receive signal SIGALRM\n");
        /*do the job when catch the sigwait*/
        break;
    default:
        break;
    }
    sigdelset(&set, SIGALRM);
    pthread_sigmask(SIG_SETMASK, &set, NULL);
 
    for(;;)
    {}
    return 0;
}
 
static void
sig_alrm(int signo)
{
    printf("after sigwait, catch SIGALRM\n");
    fflush(stdout);
    return ;
}
 
static void
sig_init(int signo)
{
    printf("catch SIGINT\n");
    return ;
}

除了返回信息方面,sigwaitinfo的行为基本上与sigwait类似。sigwait在sig中返回触发的信号值;而sigwaitinfo的返回值就是触发的信号值,并且如果info不为NULL,则sigwaitinfo返回时,还会在siginfo_t *info中返回更多该信号的信息,sigtimedwait的行为又与sigwaitinfo的行为类似,只是它多了一个超时参数timeout,也就是可以设置该函数的最大阻塞时间。siginfo_t的结构体定义如下:

int sigwaitinfo(const sigset_t *set, siginfo_t *info);
int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout);


siginfo_t 
{
    int      si_signo;    /* Signal number */
    int      si_errno;    /* An errno value */
    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 */
    uid_t    si_uid;      /* Real user ID of sending process */
    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)*/
}

向某个线程发送信号

int tkill(int tid, int sig);
int tgkill(int tgid, int tid, int sig);

linux api查询:
https://www.die.net/search/?q=kill&sa=Search&ie=ISO-8859-1&cx=partner-pub-5823754184406795%3A54htp1rtx5u&cof=FORID%3A9
参考:
https://www.cnblogs.com/mickole/p/3191804.html
https://www.cnblogs.com/chllovegeyuting/archive/2012/09/10/2679178.html
https://idc.wanyunshuju.com/cym/799.html
多进程多线程下的sigwiat
sigwait
sigwaitinfo & sigtimedwait
sigsuspend

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unix/Linux系统编程是指使用C语言编写程序,能够调用操作系统提供的系统调用和库函数来完成系统级任务的程序设计过程。Unix/Linux系统编程的目的是编写高效、可靠、安全、移植性好的应用程序或系统程序。 Unix/Linux系统编程的核心代码包括使用系统调用,文件操作(读写文件、目录操作等),进程控制(fork、exec等),信号处理,网络编程等。 在Unix/Linux中,系统调用是与内核进行通讯的标准方式。程序中使用系统调用来请求内核完成某个任务。例如,open()系统调用用于打开一个文件,并返回文件描述符。read()和write()系统调用用于读写文件。 文件操作是Unix/Linux系统编程中的一个重要部分。文件操作包括打开文件、读写文件、删除文件、重命名文件等操作。另外还有目录操作,如创建目录、删除目录、遍历目录等。 进程控制是Unix/Linux系统编程中最为复杂的部分之一。进程控制包括创建新进程、执行新进程、等待进程结束、发送信号给进程等等。其中最常见的系统调用是fork()和exec()。fork()用于创建新进程,而在创建新进程之后,exec()则用于在新进程中执行新的程序。 信号处理是Unix/Linux系统编程中的一个重要概念。信号是由系统发出的一个异步事件,可以从进程内部或外部发出。进程可以对信号进行相应操作。常见的信号包括SIGINT(Ctrl+C中断信号)、SIGTERM(终止进程信号)和SIGKILL(强制终止进程信号)。 网络编程是Unix/Linux系统编程中的另一个重要部分。Unix/Linux提供了许多网络编程API,例如socket()、bind()、listen()和accept()等。使用这些API可以编写服务器端和客户端程序,进行网络通信。 总之,Unix/Linux系统编程涉及到许多重要的概念和操作,涉及到操作系统底层的各种操作。因此,需要开发人员有扎实的C编程能力、熟悉Unix/Linux系统调用和库函数、了解进程控制和信号处理的概念、熟悉网络编程API以及充分了解操作系统内部的机制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值