跳转至

信号

信号 vs 信号量:两个没有任何关系!

是什么?

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

1.在没有发生的时候,我们已经知道怎么处理了

2.信号我们能够认识,很早之前,有人给我们大脑设置了识别特定信号的方式

1和2我们能识别一个信号并处理

3.信号到来的时候,我们正在处理更重要的事情,我们暂时不能处理到来的信号,我们必须暂时要将到来的信号进行临时保存(保存在进程的PCB中).

4.信号到了,可以不立即处理,可以在合适的时候处理

5.信号的产生是随时产生的,我们无法预料,所以信号是异步发送的.(信号的产生,是由别人(用户,进程)产生的,我收到之前,我一直在忙我的事情,并发在跑的)

1-5:进程看待信号的方式

为什么?

停止,删掉..系统要求有随时响应信号的能力,随后做出反应

怎么办?

准备

1.常见信号: 数字和名字都可以标识信号,名字真实就是宏(没有0和32、33)、34-64:实时信号---不做处理,所以常用信号1-31

2.信号处理的方式 --- signal

a.默认动作

b.自定义处理信号 --- 捕捉

c.忽略了信号 --- 是处理了信号吗?是

更改我对信号的处理方式

信号的产生

1.kill 命令(-9(SIGKILL),-2(SIGINT):进程自己终止)

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>

void handler(int signo) {
    // 简单的打印一句话
    std::cout << "get a sig, number is :" << signo << std::endl;
}

int main() {
    // signal调用完了,handler方法会被立即执行吗?不是,设置对应信号的处理方法
    // 未来我们收到对应的信号才执行handler方法
    // 未来进程永远没有收到SIGINT呢?handler也就永远不会被调用
    signal(SIGINT, handler); //自定义处理
    singal(SIGINT, SIG_IGN); //忽略
    // ctrl + c为什么可以终止程序:Ctrl + c -> 解释成信号 -> 向目标进程进行发送 -> 收到进程 -> 响应进程
    while(true) {
        std::cout << "I am active...,pid: " << getpid() << std::endl;
        sleep(1); 
    }
    return 0;
}

2.键盘

ctrl + c / ctrl + \

3.系统调用:

int kill(pid_t pid, int sig); // 对任意进程发送信号

int raise(int sig); // 对自己发送信号

void about(void); // 终止进程,对自己发送指定的信号 6) SIGABRT

unsigned int alarm(unsigned int seconds); 
// 1.alarm只响一次,如果要多次响应要嵌套调用
// 2.返回值是现在调用alarm时间距离上次一次调用alarm结束时间的差值(单位s)
// 3. alarm(0):取消闹钟 

4.软件条件

设置闹钟,其实是OS内部设定的.可能同时存在很多的闹钟.管理闹钟?先描述,再组织.

struct alarm {
    pid_t pid;
    uint64 expired;//过期时间
}

// 先描述,再组织

5.异常

a.代码除0了 8)SIGEPE

b.野指针 11)SIGSEGV

关于信号产生的各种情况的理解

键盘产生信号

a.按键按下了

b.那些按键按下了

c.字符输入(字符设备),组合键输入(输入的是命令,abcd/ctrl + c ->键盘驱动和OS进行联合解释的)

操作系统怎么知道键盘在输入数据了?

硬件中断的技术(中断向量表)

临时保存->保存在进程的PCB中->位图结构

比特位的位置,表示信号的编号,比特位的内容(0/1)是/否收到指定的信号

给进程发送信号就是写信号!!!

task_struct是内核数据结构->只有OS有资格写入->用户也想写信号?OS提供系统调用!!!

->无论信号产生有多少种,最终都是OS动手向进程写入信号

寄存器只有一个,但是寄存器的数据可以有很多,我们把寄存器的数据叫做: 上下文数据!!!

野指针->虚拟地址->转化(OS + CPU(MMU(虚拟到物理转换的)))->只读区域要写,OS直接返回报错

信号的保存

Core:终止进程

Term:termination

云服务器默认将进程Core退出,进行了特定的设定,默认Core是被关闭的.

(为什么默认关闭核心转储功能:防止未知的core dump一直在进行,导致服务器磁盘被打满)

int main() {
    int a = 10;
    a /= 0;
    return 0;
}
ulimit -c 10240 //打开Core+pid(新版没有pid)

现象会生成一个core文件

为什么?

想通过Core定位到进程为什么退出,以及执行到哪行代码退出的.

是什么?

将进程在内存中的核心数据(与调试有关)转存储中形成Core,core.pid的文件

有什么用?

协助调试(core-file core):直接定位到问题位置(事后调试)

int main() {
    pid_t id = fork();
    if (id == 0) {
        sleep(2);
        int a = 10;
        a /= 0;
        exit(0);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0) {
        cout << "exit code " << ((status >> 8) & 0xFF) << endl;
        cout << "exit signal" << (status & 0x7F) << endl;
        cout << "code dump" << ((status >> 7) & 0x1) << endl;
    }
    return 0;
}

系统调用

  • 实际执行信号的处理动作称为递达(默认、忽略、自定义)

  • 信号从产生到递达之间的状态,称为信号未决

  • 进程可以选择阻塞某个信号

(0000 0000 0000 0000 0000 0000 0000 0000 --- 比特位的位置,表示信号编号,比特位的内容,是否收到指定信号)

(0000 0000 0000 0000 0000 0000 0000 0000 --- 比特位的位置,表示信号编号,比特位的内容,是否阻塞该信号)

如果一个信号被阻塞(屏蔽),则该信号永远不会被递达处理,除非解除阻塞.

阻塞 vs 忽略区别:忽略是一种信号递达的方式,阻塞仅仅是不让信号进程递达.

阻塞一个信号,和是否收到指定信号,有关系吗?

没关系

理论:记住3张表即可.

OS->发送信号->OS想目标进程写入信号

三张表匹配的操作和系统调用

// 有没有涉及到将数据设置进内核呢?没有
// sigset_t数据类型,int double float class没有差别
sigset_t s; //用户栈上开辟了空间
sigemptyset(&s);
sigaddset(&s, 2);
return 0;
void handler(int signo) {
    cout << signo << "号信号被递达处理..." << endl;
}

void PrintSig(sigset_t &pending)
{
    cout << "Pending bitmap: ";
    for (int signo = 31; signo > 0; signo--)
    {
        if (sigismember(&pending, signo))
        {
            cout << "1";
        }
        else
            cout << "0";
    }
    cout << endl;
}

int main()
{
    // 对2号信号进行自定义捕捉 --- 不让进程因为2号信号而终止
    signal(2, handler);
    // 1.屏蔽2号信号
    sigset_t block, oblock;
    sigemptyset(&block);
    sigemptyset(&oblock);
    // 0.如果我屏蔽了所有信号呢???
    sigaddset(&block, 2); // SIGSET --- 根本就没有设置当前进程的PCB block位图中
    // 9号、19号系统无法被屏蔽;18号会被特殊处理 
    // for (int signo = 1; signo <= 31; signo++) 
    //     sigaddset(&block, signo);
    // 1.1 开始屏蔽2号信号,其实就是
    int n = sigprocmask(SIG_SETMASK, &block, &oblock);
    assert(n == 0);
    // (void)n; //骗过编译器,不要警告,因为我们后面用n,不光光是定义
    cout << "block 2 signal success" << endl;
    cout << getpid() << endl;
    int cnt = 0;
    while (true)
    {
        // 2.获取进程的pending位图
        sigset_t pending;
        sigemptyset(&pending);
        n = sigpending(&pending);
        assert(n == 0);
        // 3.打印pengding位图中收到的信号
        PrintSig(pending);
        cnt++;
        // 4.解除对2号信号的屏蔽
        if (cnt == 20) {
            cout << "解除对2号信号的屏蔽" << endl;
            n = sigprocmask(SIG_UNBLOCK, &block, &oblock); // 2号信号会被立即递达,默认处理是终止进程
            assert(n == 0);
        }

        // 我还想看到pending 2号信号1->0:递达2号信号!

        sleep(1);
    }
}

细节:

a.递达的时候,就一定会把对应的pending位图进行清0

b.先清0,再递达 还是 先递达,再清0(先清0,再递达)

信号的处理

1.信号什么时候被处理的?

合适的时候,什么是合适的时候呢?进程从内核态(操作系统的状态,权限级别高),切换会用户态(你自己的状态)的时候,信号检测并处理(进程是会被调度的,进程是有时间片的)

在信号处理的过程中,一共会有4次的状态切换(内核和用户态)

为什么我们在信号捕捉的时候,执行我们写的方法,还要从内核态转换回用户态?

用户可能会执行一些越权的非法操作

信号 --- 杀掉这个进程 --- SIG_DFL

2.信号如何被处理的?

内核态 --- 地址空间的第三讲 --- 轻一点

系统调用本质是一堆函数指针数组

1.我们使用系统调用或者访问系统数据,其实还是在我进程的地址空间内进行跳转的

2.进程无论如何切换,总能找到OS(我们访问OS,本质就是通过我的进程的地址空间的[3,4]GB)来访问即可

3.操作系统是如何正常运行的?->信号技术本来就是通过软件的方式,来模拟软件中断(非常高频率的,每个非常短的时间,就给CPU发送中断---cpu不断地进行处理中断).->谁让OS运行起来呢?

4.操作系统不相信任何用户

3.捕捉信号还有其他方式吗?signal?

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

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字

如果我们处理对应的信号,该信号默认也会从信号屏蔽字中进行移除

不想让信号,嵌套式进行捕捉处理

在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明;

可重入函数

该函数执行流重复进入了,导致产生了问题:该函数我们叫做不可重入函数,否则叫做可重入函数

我们用到的大部分函数都是不可重入的!

void Print(sigset_t& pending) {
    cout << "cur process pending:";
    for (int sig = 31; sig > 0; sig--) {
        if (sigismember(&pending, sig))
            cout << "1";
        else
            cout << "0";
    }
    cout << endl;
}

void handler(int signo) {
    cout << "signo: " << signo << endl;
    // 不断获取当前进程的pending信号集合并打印
    sigset_t pending;
    sigemptyset(&pending);
    while (true) {
        sigpending(&pending);
        Print(pending);
    }
int g_flag = 0;

void changeflag(int signo) {
    (void)signo;
    printf("将flag,从%d->%d\n", g_flag, 1);
    g_flag = 1;
}

int main() {
    signal(2, changeflag);
    while (!g_flag); // 故意写成这个样子,编译器默认会对我们的代码进行自动优化(实际上就是把内存中的g_flag放入寄存器导致g_flag实际改变了也不会改变g_flag,volatile可以不进行优化)
    printf("process quit normal\n");
    return 0;
}

SIGCHLD信号

子进程退出,不是默默退出的,会在退出的时候,向父进程发送信号,发送17)SIGCHLD信号

如何证明?

void handler(int signo) {
    cout << "child quit, father get a signo: " << signo << endl; 
}

int main() {
    signal(SIGCHLD, handler);
    pid_t pid = fork();
    if (pid == 0) {
        //child
        int cnt = 5;
        while (cnt--) {
            cout << "I am Child process: " << getpid() << endl;
            //sleep(1);
        }
        cout << "child process died" << endl;
        exit(0);
    }
    while (true)
        sleep(1);
    return 0;
}

如何在多个进程下都成功释放,不变成僵尸进程

void CleanupChild(int signo)
{
    if (signo == SIGCHLD)
    {
        while (true)
        {
            pid_t rid = waitpid(-1, nullptr, WNOHANG); // -1回收任意一个子进程
            if (rid > 0)
            {
                cout << "wait child success" << rid << endl;
            }
            else
                break;
        }
    }
    cout << "wait sub process done" << endl;
}
int main()
{
    signal(SIGCHLD, CleanupChild);
    for (int i = 1; i <= 100; ++i)
    {
        pid_t pid = fork();
        if (pid == 0)
        {
            // child
            int cnt = 5;
            while (cnt--)
            {
                cout << "I am Child process: " << getpid() << endl;
                sleep(1);
            }
            cout << "child process died" << endl;
            exit(0);
        }
    }
    while (true)
        sleep(1);
    return 0;
}

方法二:

int main()
{
    signal(SIGCHLD, SIG_IGN);
    for (int i = 1; i <= 100; ++i)
    {
        pid_t pid = fork();
        if (pid == 0)
        {
            // child
            int cnt = 5;
            while (cnt--)
            {
                cout << "I am Child process: " << getpid() << endl;
                sleep(1);
            }
            cout << "child process died" << endl;
            exit(0);
        }
    }
    while (true)
        sleep(1);
    return 0;
}