Linux——进程信号

信号: Linux系统提供的让用户(进程)给其他进程发送异步信息的一种方式

  1. 在没有发生的时候,进程已经知道发生的时候如何处理
  2. 信号到来的时候,可能进程正在处理更重要的事情,暂时可能无法处理到来的信号,必须暂时将到来的信号进行临时保存
  3. 信号到了可以不立即处理,可以在合适的时候处理
  4. 信号的产生是随时产生的,无法预料,所以信号是异步发送的

·1. 常见信号:
在这里插入图片描述
2. 信号的产生

  1. kill命令
kill -signum pid
// signum —— 进程信号
// pid —— 进程id
  1. 键盘产生信号
ctrl + c   ---->  操作系统解释为2号信号  ----> 向目标进程进行发送  ----> 进程收到  ----> 进程响应
  1. 系统调用
对任意进程发送任意的信号

在这里插入图片描述

对自己发送任意的信号

在这里插入图片描述

直接终止进程,对自己发送指定的信号

在这里插入图片描述

  1. 软件条件

在这里插入图片描述

实现一个mykill命令

#include <iostream>
#include <signal.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
using namespace std;

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        cout << "Usage: " << argv[0] << " -signumber  pid " << endl;
        return 1;
    }

    int signumber = stoi(argv[1] + 1);
    int pid = stoi(argv[2]);
    int n = kill(pid, signumber);
    if(n<0)
    {
        cerr<<"kill error, "<< strerror(errno)<< endl;
    }
    return 0;
}

3. 信号处理方式 ——signal

  1. 默认动作
  2. 自定义处理信号——捕捉
  3. 忽略信号
#include<signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
// signum : 对哪个信号进行捕捉
// handler: handler方法
// 返回值:旧的处理方法
void handler(int signo)
{
	//打印收到的信号
    cout<< "get a sig, number is : "<< signo << endl;
}
int main()
{
	// signal调用完了不会立即执行handler方法,它是在设置对应信号的处理方法
	// 未来在收到对应的信号才会执行handler方法
	// 如果一直没有收到SIGNT信号,则handler方法永远不会被调用
    signal(SIGINT,handler);
    //signal(SIGINT,SIG_IGN);     // ignore
    cout<<"I am activing..., pid: "<< getpid()<< endl;
    return 0;
}

4. 信号其他相关常见概念

  • 实际执行信号的处理动作称为信号递达(Delivery)
  • 信号从产生到递达之间的状态,称为信号未决(Pending)
  • 进程可以选择阻塞(Block)某个信号
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作
  • 阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

5. sigset_t

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统
实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做
任何解释,比如用printf直接打印sigset_t变量是没有意义的

#include <signal.h>
// 对位图集合进行清空,全部清零
int sigemptyset(sigset_t *set);
// 对位图集合进行置1
int sigfillset(sigset_t *set);
// 将指定的信号添加到指定集合中
int sigaddset (sigset_t *set, int signo);
// 将特定信号从特定集合中删除
int sigdelset(sigset_t *set, int signo);
// 判定一个信号是否在该集合当中
int sigismember(const sigset_t *set, int signo);

6. sigprocmask

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信
号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后
根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

SIG_BLOCKset包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set
SIG_UNBLOCKset包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
SIG_SETNASK设置当前信号屏蔽字为set所指向的值,相当于mask=set

7. sigpending

#include <signal.h>
int sigpending(sigset_t *set);
//读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。 

使用上述函数将2号信号屏蔽,并在10s后解除屏蔽并递达,代码如下:

#include <iostream>
#include <signal.h>
#include<unistd.h>
using namespace std;
void Print(sigset_t pending)
{
    cout<<"Pending bitmap: ";
    for (int sig = 31; sig >= 1; sig--)
    {
        if (sigismember(&pending, sig))
            cout << "1";
        else
            cout << "0";
    }
    cout << "\n";
}
void handler(int signo)
{
    cout<< "signo: "<< signo<<" 号信号被递达处理..." <<  endl;
}
int main()
{
    signal(2,handler);

    // 屏蔽2号信号
    sigset_t block, oblock;
    sigemptyset(&block);
    sigaddset(&block, 2);
    // 开始屏蔽2号信号,设置进内核中
    sigprocmask(SIG_SETMASK, &block, &oblock);
    cout << "block 2 success " << endl;
    cout<< "pid: "<< getpid()<<endl;

    int cnt = 0;
    while (true)
    {
        // 获取进程的pending位图
        sigset_t pending;
        sigemptyset(&pending);
        sigpending(&pending);
        // 打印pending位图中收到的信号
        Print(pending);
        cnt++;

        if(cnt == 10 )
        {
            cout<< "解除对2号信号的屏蔽"<<endl;
            sigprocmask(SIG_UNBLOCK,&block,&oblock);
        }
        sleep(1);
    }
    return 0;
}

8.sigaction

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
// const struct sigaction *act 是输入参数(要设置的新的处理方法)     
// struct sigaction *oact 是输出参数(旧的处理方法)
struct sigaction
{
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);   // 处理实时信号
    sigset_t sa_mask;                                 
    // 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字
    int sa_flags;
    void (*sa_restorer)(void);
};

sigaction函数可以读取和修改与指定信号相关联的处理动作。
sigaction调用成功返回0,出错返回-1.

void print(sigset_t& pending)
{
    cout<< "cur process pending :  ";
    for(int sig = 31;sig>=1;sig--)
    {
    	//判定指定信号是否在集合中
        if(sigismember(&pending, sig))   cout<<"1";
        else cout<<"0";
    }
    cout<< "\n";
    sleep(1);
}

void handler(int signo)
{
    cout<<"signal : "<<  signo  <<endl;
    // 不断获取当前进程的pending信号集合并打印
    sigset_t pending;
    sigemptyset(&pending);
    while(true)
    {
	    sigpending(&pending);
	    print(pending);
    }
}
int main()
{
    struct sigaction act,oact;
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    //如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,
    //则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
    // 向sa_mask中添加信号
    sigaddset(&act.sa_mask,3);
    sigaddset(&act.sa_mask,4);
    sigaddset(&act.sa_mask,5);
    sigaction(2,&act,&oact);
    while(true)   sleep(1);
    return 0;
}

9.SIGCHLD信号

子进程退出,父进程不wait,子进程就会变成僵尸状态。
子进程退出,不是默默退出的,会在退出的时候,会向父进程发送17)SIGCHILD信号。
我们可以通过如下代码看到子进程退出会给父进程发送信号。

void handler(int signo)
{
    cout<<"child quit, father get a signo : "<< signo<< endl;
}
int main()
{
    signal(SIGCHLD,handler);
    pid_t id = fork();
    if (id == 0)
    {
        // 子进程
        int cnt = 5;
        while (cnt)
        {
            cout << "I am process : " << getpid() << endl;
            cnt--;
            sleep(1);
        }

        cout<< "child process died "<<endl;
        exit(0);
    }
    while(true)  sleep(1);
}

由于父进程一般情况都在使用waitpid/wait阻塞或者非阻塞轮询等待子进程,如果子进程在退出时给父进程发送了信号,那么父进程可以将回收的方法放在信号处理的函数当中,在子进程退出发送信号时,主进程在对子进程进行回收。因此可以将handler函数改为如下函数来对子进程进行回收处理:

void cleanupChild(int signo)
{
    if (signo == SIGCHLD)
    {
        pid_t rid = waitpid(-1, nullptr, 0); // -1 —— 等待任何一个子进程
        if (rid > 0)
        {
            cout << "wait child success" << endl;
        }
    }
}

但如果是多个进程,则上述代码存在无法将所有子进程均回收的问题。因此更改如下:

void cleanupChild(int signo)
{
    if (signo == SIGCHLD)
    {
        while (true)
        {
            pid_t rid = waitpid(-1, nullptr, WNOHANG); // -1 :等待任何一个子进程     WNOHANG: 非阻塞等待
            if (rid > 0)
            {
                cout << "wait child success" << endl;
            }
            else if(rid <= 0 ) break;
        }
    }
}

10.volatile关键字: 修饰变量时,无论编译器如何优化,禁止把变量优化到寄存器中,检测访问变量时必须从内存中进行,不能从CPU寄存器中读取,保持内存的可见性。

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值