Linux-进程信号

本文详细解释了Linux中的信号概念,包括信号的产生(如硬件中断和软件kill命令)、注册、注销以及处理机制,特别关注了自定义信号处理和特殊信号如SIGKILL和SIGSTOP。还通过实例展示了如何自定义信号处理、阻塞和解除阻塞信号,以及信号在SIGCHLD和SIGPIPE中的基本应用。
摘要由CSDN通过智能技术生成

概念

进程信号:
概念:信号就是软件中断。信号就是用于向进程通知某个事件的产生,打断进程当前操作,去处理这个事件。
linux中信号的种类:使用kill -l命令查看所有信号–62种
1~31:非可靠信号(有可能会造成事件丢失)
34~64:可靠信号(不会丢失事件)
信号的生命周期:产生信号->在进程pcb中注册信号->注销信号->处理信号

信号产生

信号的产生
硬件产生
ctrl+c 退出进程
ctrl+| 退出进程
ctrl+z 暂停进程,此时ps -aux | grep 进程名称 可以看到进程处于停止状态。jobs:查看作业,通过fg 作业id:让停止作业继续运行
软件产生
kill命令发送信号给指定进程 kill -signum pid
kill命令杀死一个进程的原理:默认给进程发送了终止信号
int kill(pid_t pid, int sig); 给pid进程发送sig信号
int raise(int sig); – 给进程自身发送一个指定的信号
unsigned int alarm(unsigned int seconds);
–sec秒之后给进程自身发送一个时钟信号–SIGALRM
void abort(void); – 给进程自身发送一个SIGABRT信号
int sigqueue(pid_t pid, int sig, const union sigval value);
给一个进程发送信号的同时携带一个数据过去

信号注册

信号的注册
注册:在进程中注册一个信号让进程直到自己收到了某个信号
修改pending位图(用于标记是否收到了某个信号),添加一个信号信息节点
非可靠:如果信号没有被注册,则注册;否则什么都不做。链表中不会出现相同非可靠信号信息节点
可靠:不管信号是否注册,都会注册一下。链表中有可能又多个相同的信号信息节点。
sigqueue-双向链表 – 表示有多少信号

信号注销

信号的注销
注销:将信号信息进程pcb中一处(修改位图,删除节点)
非可靠:删除节点,修改位图为0
可靠:删除一个信号节点,检查链表中是否还有相同节点,没有则修改位图

信号处理

信号的处理
处理:信号的处理也叫信号的递达,实际上就是打断进程当前的操作,去执行进程的对应信号处理函数。
信号的处理方式:
1.默认处理方式
2.忽略处理方式
3.自定义处理方式–用户自己定义信号的处理回调函数
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum:信号值-表示要修改哪个信号的处理方式
handler:新的信号处理方式
SIG_DEL-默认;SIG_IGN-忽略;自定义函数的名称
返回值:成功则返回当前信号原来的处理方式

自定义处理方式的信号捕捉流程
信号是从程序运行从内核态返回用户态之前处理的。
程序处理信号时在内核态,当遇到回调函数自定义时,因为自定义函数是用户自己写的,返回用户态执行,完成后如果还有信号则继续返回内核态进行,当信号处理完成后返回用户态主流程。
程序运行可以通过中断,异常,系统调用从用户态运行切换到内核态

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

void sigcb(int signo)
{
	printf("recv signal:%d\n", signo);
	printf("时间到了\n");
	alarm(3);
}
int main(int argc, char *argv[])
{
	signal(信号,处理方式)
	signal(SIGALRM, sigcb);
	kill(进程ID,信号值)
	//kill(getpid(), SIGINT);
	//raise(SIGINT);
	alarm(3);
	while(1)
	{
		printf("-----\n");
		sleep(1);
	}
	return 0;
}

阻塞:信号的阻塞–阻止信号被递达
一个信号被阻塞后,依然收到这个信号会注册,但是暂时不被处理
pcb中有pending位图-未决信号结合;还有阻塞信号集合
如果要阻塞一个信号,就是在进程的阻塞信号集合中标记这个信号
具体操作:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how:操作类型
SIG_BLOCK-- 阻塞set集合的信号 block = block | set
SIG_UNBLOCK – 将set集合中的信号解除阻塞 block &= ~set
SIG_SETMASK–将set集合中的信号设置为阻塞集合的信号block=set
返回值:成功返回0;失败返回-1

实例

先自定义特定信号的处理方式–做打印收到了哪个信号 signal
将所有信号阻塞 sigprocmask block
让程序运行停下来,在这期间给进程发送信号getchar
解除这些信号的阻塞,查看信号处理结果 sigprocmask unblock

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


void sigcb(int signo)
{
        printf("recv signal:%d\n", signo);
}
int main(int argc, char *argv[])
{
        signal(SIGINT, sigcb);
        signal(40, sigcb);

        sigset_t set;
        sigemptyset(&set);
        sigfillset(&set);
        //zu se
        sigprocmask(SIG_BLOCK, &set, NULL);
        printf("回车后,继续运行\n");
        getchar();
        printf("解除信号阻塞,查看结果\n");
        sigprocmask(SIG_UNBLOCK, &set, NULL);

        while(1)
        {
                sleep(1);
        }
        return 0;
}

通过实例,按三次ctrl c,输入四次kill -40命令。最终结果显示ctrl c一次,kill -40四次。
因为ctrl c是SIGINT,在前31属于不可靠信号,因此同时发送阻塞只保留一个。kill -40属于可靠信号,每次发送都保留,等阻塞结束后依次回调。

两个比较特殊的信号:SIGKILL/SIGSTOP 这两个信号不可被阻塞,不可被自定义,不可被忽略,说白了就是无法修改处理方式。

信号的基本应用

信号的基本应用:
SIGCHLD信号:一个子进程退出后,给父进程发送的子进程状态改变信号
但是SIGCHLD默认处理方式就是什么都不做
要避免僵尸进程,则需要在父进程中wait阻塞等待。
如果不想阻塞等待,则可以使用信号来解决
自定义SIGCHLD信号的处理方式,在回调函数中调用waitpid接口
SIGCHLD是一个非可靠信号–意味着多个子进程同时退出,有可能丢失事件
signal(SIGCHLD,SIG_IGN);–显式忽略

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/wait.h>

void sigcb(int no)
{
        printf("son fork exit\n");
        waitpid(-1, NULL, 0);
}
int main(int argc, char *argv[])
{
        signal(SIGCHLD, sigcb);

        pid_t pid = fork();
        if(pid == 0)
        { 
                sleep(3);
                exit(0);
        }
        while(1)
        {
                printf("------\n");
                sleep(1);
        }
        return 0;
}

waitpid();有子进程退出返回值>0;没有子进程退出返回值==0;出错<0
wait(-1, NULL, WNOHANG)
-1:等待任意一个子进程的退出
NULL:返回值不关心
WNOHANG:将接口设置为非阻塞

SIGPIPE:管道所有读端被关闭则write出发异常对应的信号
SIGPIPE信号默认处理方式就是退出进程,若不想退出则需要自定义/忽略

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<string.h>
#include<signal.h>


void sigcb(int no)
{
        printf("all pipe read is close\n");
}
int main(int argc, char *argv[])
{
//更换信号处理方式
        signal(SIGPIPE, sigcb);
        umask(0);
        char *fifo_name = "./test.fifo";
        int ret = mkfifo(fifo_name, 0664);
        if(ret < 0 && errno != EEXIST)
        {
                perror("mkfifo error");
                return -1;
        }

        int fd = open(fifo_name, O_WRONLY);
        if(fd < 0)
        {
                perror("open error");
                return -1;
        }
        while(1)
        {
                char buf[1024] = {0};
                scanf("%s", buf);
                int ret = write(fd, buf, strlen(buf));
                if(ret < 0)
                {
                        perror("write error");
                        return -1;
                }
        }
        close(fd);
        return 0;
}

关键字:volatile
功能:保持内存可见性–让cpu每次访问变量的时候都从内存中重新获取数据
目的:防止编译器过度优化

可重入函数与不可重入函数:
函数的重入:在不同的执行流程中(main、signal)同时进入一个函数进行执行。
不可重入:一个函数重入后,有可能会造成数据二义或者逻辑混乱。
可重入:一个函数重入后,不会出现问题。
函数是否可重入的重点:一个函数中是否对全局数据进行不受保护的非原子操作(是-不可重入)

//不可重入
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>

int a = 0, b = 0;

int test()
{
        a++;
        sleeo(1);
        b++;
        printf("%d + %d = %d\n",a, b, a+b);
}

void sigcb(int no)
{
        test();
}

int main(int argc, char *argv[])
{
        signal(SIGINT, sigcb);
        test();
        return 0;
}
  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值