kill()
- 给指定进程发送指定信号
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
- pid 进程编号
- sig 信号
/*************************************************************************
> File Name: callKill.c
> Author:
> Mail:
> Created Time: Sat 29 Jan 2022 09:48:15 PM CST
************************************************************************/
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid) {
sleep(5);
if (kill(pid, SIGQUIT) < 0) {
perror("kill");
exit(1);
}
int status;
wait(&status);
if (WIFSIGNALED(status)) {
printf("child terminate by signal %d\n", WTERMSIG(status));
} else {
printf("child exit with other reason %d\n", WTERMSIG(status));
}
} else {
while (1) {
sleep(1);
printf("父进程你快点杀我~~~\n");
}
}
return 0;
}
raise()
给当前进程发送信号
等价 == kill(getpid(), sig);
#include <signal.h>
int raise(int sig);
/*************************************************************************
> File Name: callKill.c
> Author:
> Mail:
> Created Time: Sat 29 Jan 2022 09:48:15 PM CST
************************************************************************/
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid) {
int status;
wait(&status);
if (WIFSIGNALED(status)) {
printf("child terminate by signal %d\n", WTERMSIG(status));
} else {
printf("child exit with other reason %d\n", WTERMSIG(status));
}
} else {
int n = 10;
while (n--) {
sleep(1);
printf("父进程我还有%d秒就要死了~~~\n", n);
}
raise(9); //发送信号9,杀死子进程
}
return 0;
}
abort()
- 使当前进程接收SIGABRT信号而异常终止
#include <stdlib.h>
void abort(void);
alarm()
- 调用alarm()函数可以设定一个闹钟,也就是告诉内核在secondes秒之后给当前进程发SIGALRM信号。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
/*************************************************************************
> File Name: callAlarm.c
> Author:
> Mail:
> Created Time: Sat 29 Jan 2022 10:37:22 PM CST
************************************************************************/
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
alarm(5);
sleep(2);
size_t n = alarm(5); //剩余3s
printf("剩余 %lds\n", n);
char i;
for (i = 0; i > -1; i++) {
printf("i = %d\n", i);
}
printf("i == %d\n", i);
return 0;
}
阻塞信号
实际执行信号处理的动作称为信号递达(delivery),信号从产生到递达之间的状态,称为信号未决(pending)。
进程可以选择阻塞(block)某个信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才解除该标志。
阻塞信号集
- 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?
- Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。
- 阻塞信号集也叫做当前进程的信号屏蔽字。(signal mask)
API
#include <signal.h>
int sigemptyset(sigset_t *set); //信号集置空
int sigfillset(sigset_t *set); // 初始化设置为full,包括所有信号。
int sigaddset(sigset_t *set, int signum); //往信号集set增加信号signum
int sigdelset(sigset_t *set, int signum); //往信号集set除去信号signum
int sigismember(const sigset_t *set, int signum); //测试signum是否是信号集的成员。
sigprocmask()
- 该函数可以读取或更改进程的信号屏蔽字
#include <signal.h>
/* Prototype for the glibc wrapper function */
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
/* Prototype for the underlying system call */
int rt_sigprocmask(int how, const kernel_sigset_t *set,
kernel_sigset_t *oldset, size_t sigsetsize);
/* Prototype for the legacy system call (deprecated) */
int sigprocmask(int how, const old_kernel_sigset_t *set,
old_kernel_sigset_t *oldset);
- set 是你要增加或去除的信号集
- oldset是原来的阻塞信号集
- how有三个宏:
- SIG_BLOCK : 阻塞信号集是当前集合oldset和集合set参数的并集。也就是
- SIG_UNBLOCK : 集合中的信号从当前阻塞信号集合中移除。允许尝试解除未被阻断的信号。
- SIG_SETMASK : 阻塞信号集被设置为参数set。
- 如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
代码
/*************************************************************************
> File Name: sigMask.c
> Author:
> Mail:
> Created Time: Sun 30 Jan 2022 03:01:10 PM CST
************************************************************************/
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
sigset_t set, oldset;
//oldset表示当前信号集
sigemptyset(&set); //清空信号集合
sigaddset(&set, SIGINT); //信号加入集合
sigprocmask(SIG_BLOCK, &set, &oldset); //宏SIG_BLOCK表示阻塞信号
int n = 10;
while (n--) {
sleep(1);
printf("在%ds内ctrl + c 杀不死沃~\n", n);
}
sigprocmask(SIG_SETMASK, &oldset, NULL); //恢复原来信号集
while (1) {
sleep(1);
printf("按下ctrl + c就能结束程序,不要结束沃~~\n");
}
return 0;
}
未决信号集
sigpending读取当前进程的未决信号集,通过set参数传出。
#include <signal.h> int sigpending(sigset_t *set);
调用成功返回0,否则返回-1.
代码
/*************************************************************************
> File Name: sigMask.c
> Author:
> Mail:
> Created Time: Sun 30 Jan 2022 03:01:10 PM CST
************************************************************************/
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
//打印未决信号集
void outSig(const sigset_t* set) {
for (int i = 1; i < 32; i++) {
if(sigismember(set, i)) {
putchar('1');
} else putchar('0');
}
putchar(10);
return ;
}
int main() {
sigset_t set, oldset, pendSet;
//oldset表示当前信号集
sigemptyset(&set); //清空信号集合
sigaddset(&set, 2); //信号加入集合
//sigaddset(&set, SIGSEGV);
sigprocmask(SIG_BLOCK, &set, &oldset); //宏SIG_BLOCK表示增加阻塞信号
//sigpending(&pendSet); //取出未决信号集
//raise(SIGSEGV); //给当前进程发送段错误信号
int n = 10;
while (n--) {
sleep(1);
printf("在%ds内ctrl + c 杀不死沃~\n", n);
sigpending(&pendSet);
outSig(&pendSet);
}
sigprocmask(SIG_SETMASK, &oldset, NULL); //恢复原来信号集
while (1) {
sleep(1);
printf("按下ctrl + c就能结束程序,不要结束沃~~\n");
}
return 0;
}
捕捉信号
- 如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
- 由于信号处理函数的代码是在用户空间(没有捕捉信号的话,是在内核),处理过程比较复杂。
流程
sigaction()
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
- sa_handler指定要与signum关联的操作,可能是de-fault操作的SIG_DFL、忽略此信号的SIG_IGN或指向信号处理函数的指针。此函数接收信号号作为其唯一参数。
- sa_mask指定一个信号掩码,在信号处理程序执行期间,该掩码应被阻塞(即,添加到调用信号处理程序的线程的信号掩码中)。此外,触发处理器的信号将被阻止,除非使用SA_NODEFER标志。
/*************************************************************************
> File Name: sigMask.c
> Author:
> Mail:
> Created Time: Sun 30 Jan 2022 03:01:10 PM CST
************************************************************************/
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
void sigHandler(int sigmember) {
printf("略略略,sigmemer = %d,你不行\n", sigmember);
return ;
}
int main() {
struct sigaction newac, oldac;
//oldac保存当前状态
newac.sa_handler = &sigHandler;
newac.sa_flags = 0;
sigemptyset(&newac.sa_mask);
sigaction(SIGINT, &newac, &oldac);
int n = 10;
while (n) {
sleep(1);
n--;
}
sigaction(SIGINT, &oldac, NULL); //不需要保存当前状态,置NULL
return 0;
}
pause()
- 阻塞进程
#include <unistd.h>
int pause(void);
- 如果信号的处理动作是终止进程,则进程终止,没有返回值;
- 如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;
- 如果信号的处理动作是捕捉,则调用信号的处理函数之后pause返回-1,error设置为EINTR,表示”被信号中断“,所以pause只有出错的返回值。
实现sleep()函数
/*************************************************************************
> File Name: mySleep.c
> Author:
> Mail:
> Created Time: Sun 30 Jan 2022 05:16:54 PM CST
************************************************************************/
#include<stdio.h>
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
void sigAlarm(int sigmember) { return ; }
unsigned int mySleep(unsigned int seconds) {
struct sigaction newac, oldac;
newac.sa_handler = &sigAlarm;
newac.sa_flags = 0;
sigemptyset(&newac.sa_mask);
sigaction(SIGALRM, &newac, &oldac); //替换SIGALRM的默认处理动作,因为它的默认处理动作是alarm结束后结束进程。
alarm(seconds);
pause();
sigaction(SIGALRM, &oldac, NULL);
return alarm(0); //返回剩余时间
}
int main() {
int n = 5;
while (n) {
printf("hello my sleep 1s\n");
mySleep(1);
--n;
}
return 0;
}
sigsuspend()
- 该函数包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend函数而不是pause。
#include <signal.h>
int sigsuspend(const sigset_t *mask);
简单实现sleep
/*************************************************************************
> File Name: mySleep.c
> Author:
> Mail:
> Created Time: Sun 30 Jan 2022 05:16:54 PM CST
************************************************************************/
#include<stdio.h>
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
void sigAlarm(int sigmember) { return ; }
unsigned int mySleep(unsigned int seconds) {
struct sigaction newac, oldac;
newac.sa_handler = &sigAlarm;
newac.sa_flags = 0;
sigemptyset(&newac.sa_mask);
sigset_t newmask, oldmask, susmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
//阻塞闹钟的信号
sigaction(SIGALRM, &newac, &oldac); //替换SIGALRM的默认处理动作,因为它的默认处理动作是alarm结束程序
alarm(seconds);
susmask = oldmask;
sigdelset(&susmask, SIGALRM);
//临时设置信号屏蔽字为susmask,并且立刻挂起程序等待信号(原子操作)
sigsuspend(&susmask);
unsigned int t = alarm(0);
sigaction(SIGALRM, &oldac, NULL); //还原信号动作
sigprocmask(SIG_SETMASK, &oldmask, NULL); //恢复原来信号屏蔽字
return t;
}
int main() {
int n = 5;
while (n) {
printf("hello my sleep 1s\n");
mySleep(1);
--n;
}
return 0;
}
事实上,想要不产生僵尸进程的另一种方法:父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。此方法对Linux可用,不保证在其它UNIX系统可用。