Linux知识点 -- 进程信号(二)

news2024/7/6 18:11:25

Linux知识点 – 进程信号(二)

文章目录

  • Linux知识点 -- 进程信号(二)
  • 一、信号保存
    • 1.相关概念
    • 2.信号保存的相关接口
    • 3.对所有的信号都进行自定义捕捉
    • 4.将2号信号block,并打印pending信号集
    • 5.将所有信号都block
  • 二、处理信号
    • 1.信号处理的时机
    • 2.信号处理的流程
    • 3.sigaction
  • 三、可重入函数
  • 四、volatile关键字
  • 五、SIGCHILD信号


一、信号保存

1.相关概念

  • 信号递达(Delivery):实际执行信号的处理动作;

  • 信号未决(Pedning):信号从产生到递达之间的状态;信号未决就是进程收到了一个信号,但是未处理,就是临时保存到了进程PCB中的对应的位图中;

  • 进程可以选择阻塞(block)某个信号;

  • 被阻塞的信号产生时将保持在未决状态,直到进程解决对此信号的阻塞,才执行递达的动作;

  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在抵达之后可选的一种处理动作;

  • 在进程PCB内部有3张表:
    在这里插入图片描述
    其中,pending就是信号未决的位图,进程在收到一个信号后,会将pending表中相应的位置位;
    handler是函数指针数组 – 数组下标对应信号的编号,就是对应信号的处理方式;signal自定义捕捉就是将信号对应的方法填入handler表;

    在这里插入图片描述
    也可以设置信号的忽略和默认;IGN是忽略;DFL是默认;
    block表是阻塞表,结构和pending一摸一样,代表的含义是对应的信号是否被阻塞;

  • 信号的处理过程:
    进程在接受一个信号后,会将pending表中相应的位置位,然后先去block表中查看该进程是否被阻塞,如果被阻塞,就不做任何动作,如果没有阻塞,再去handler表中查询处理方法;

2.信号保存的相关接口

(1)语言会为我们提供.h.hpp和语言的自定义类型;
同时,操作系统也会给我们提供.h和自定义类型;

(2)OS向我们提供了接口,一定要提供相对应的类型;
语言提供了访问系统调用的接口,也一定会提供相对应的类型;

  • sigset_t类型:
    未决和阻塞标志可以使用相同的数据类型(位图),sigset_t称为信号集,这个类型可以表示每个信号的有效或无效状态;
    在阻塞信号集中有效和无效的含义是该信号是否被阻塞,阻塞信号集也叫做信号屏蔽字
    而在未决信号集中有效和无效的含义是该信号是否处于未决状态;

    注:
    sigset_t不允许用户自己进行位操作,OS为我们提供了对应的操作方法;
    sigset_t使用者可以直接使用该类型,和用内置类型、自定义类型没有任何差别;
    sigset_t一定需要对应的系统接口,来完成对应的功能,其中系统接口需要的参数,可能就包含了sigset_t定义的变量或者对象;

  • OS提供的对sigset_t操作的接口:
    在这里插入图片描述
    分别是:
    全部位清0;
    全部位置1;
    某个信号置位;
    某个信号复位;
    判断信号是否存在;

    在这里插入图片描述
    sigpending函数获取当前调用进程的pending信号集;
    set是输出型参数;
    成功返回0,失败返回-1;

    在这里插入图片描述
    sigprocmask函数检查并更改block信号集;
    how参数:
    在这里插入图片描述
    set:根据how的不同的宏,有不同的功能;
    oldset:输出型参数,返回老的信号屏蔽字,不需要可以传空指针;

3.对所有的信号都进行自定义捕捉

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

using namespace std;


void catchSig(int signum)
{
    cout << "获得了一个信号:" << signum << endl;
}


int main()
{
    for(int i = 1; i <= 31; i++)
    {
        signal(i, catchSig);
    }

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

运行结果:
在这里插入图片描述
可以发现,其他信号都被自定义捕捉了,只有9号信号杀死了该进程,因为9号信号是不能被捕捉的

4.将2号信号block,并打印pending信号集

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cassert>

using namespace std;

static void showPending(sigset_t &pending)
{
    for(int sig = 1; sig <= 31; sig++)
    {
        if(sigismember(&pending, sig))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << endl;
}

int main()
{
    //1.定义信号集对象
    sigset_t bset, obset;
    sigset_t pending;
    //2.初始化
    sigemptyset(&bset);
    sigemptyset(&obset);
    sigemptyset(&pending);
    //3.添加要进行屏蔽的信号
    sigaddset(&bset, 2);
    //4.设置set到内核中对应的进程内部
    int n = sigprocmask(SIG_BLOCK, &bset, &obset);
    assert(n == 0);
    (void)n;

    cout << "block 2号信号成功 " << endl;

    //5.重复打印当前进程的pending信号集
    while(true)
    {
        //获取当前进程的pending信号集
        sigpending(&pending);
        //显示当前进程的pending信号集
        showPending(pending);
        sleep(1);
    }

    return 0;
}

运行结果:
在这里插入图片描述
在这里插入图片描述
当发送了2号信号后,pending表中对应的位置1了,2号信号是被阻塞了,应该一直在pending表中,无法被递达;

在一定时间后恢复2号信号的block

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cassert>

using namespace std;

static void showPending(sigset_t &pending)
{
    for(int sig = 1; sig <= 31; sig++)
    {
        if(sigismember(&pending, sig))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << endl;
}

int main()
{
    //1.定义信号集对象
    sigset_t bset, obset;
    sigset_t pending;
    //2.初始化
    sigemptyset(&bset);
    sigemptyset(&obset);
    sigemptyset(&pending);
    //3.添加要进行屏蔽的信号
    sigaddset(&bset, 2);
    //4.设置set到内核中对应的进程内部
    int n = sigprocmask(SIG_BLOCK, &bset, &obset);
    assert(n == 0);
    (void)n;

    cout << "block 2号信号成功 " << endl;

    //5.重复打印当前进程的pending信号集
    int count = 0;
    while(true)
    {
        //获取当前进程的pending信号集
        sigpending(&pending);
        //显示当前进程的pending信号集
        showPending(pending);
        sleep(1);
        count++;
        if(count == 20)
        {
            int n = sigprocmask(SIG_SETMASK, &obset, nullptr);//将原来的信号集附上去
            assert(n == 0);
            (void)n;
            cout << "接触对2号信号的block " << endl;
        }
    }

    return 0;
}

运行结果:
在这里插入图片描述
结果是没有看到pending表从1变为0;
默认情况下,回复对于2号信号block的时候,确实会进行递达;
但是2号信号的默认处理动作是终止进程,将进程直接终止;

我们需要对2号信号进行捕捉:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cassert>

using namespace std;


void catchSig(int signum)
{
    cout << "获得了一个信号:" << signum << endl;
}

static void showPending(sigset_t &pending)
{
    for(int sig = 1; sig <= 31; sig++)
    {
        if(sigismember(&pending, sig))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << endl;
}

int main()
{
    signal(2, catchSig);

    //1.定义信号集对象
    sigset_t bset, obset;
    sigset_t pending;
    //2.初始化
    sigemptyset(&bset);
    sigemptyset(&obset);
    sigemptyset(&pending);
    //3.添加要进行屏蔽的信号
    sigaddset(&bset, 2);
    //4.设置set到内核中对应的进程内部
    int n = sigprocmask(SIG_BLOCK, &bset, &obset);
    assert(n == 0);
    (void)n;

    cout << "block 2号信号成功 " << endl;

    //5.重复打印当前进程的pending信号集
    int count = 0;
    while(true)
    {
        //获取当前进程的pending信号集
        sigpending(&pending);
        //显示当前进程的pending信号集
        showPending(pending);
        sleep(1);
        count++;
        if(count == 20)
        {
            int n = sigprocmask(SIG_SETMASK, &obset, nullptr);//将原来的信号集附上去
            assert(n == 0);
            (void)n;
            cout << "接触对2号信号的block " << endl;
        }
    }

    return 0;
}

在这里插入图片描述
注:
没有一个接口时用来设置pending位图的,这是因为所有信号的发送方式,都是修改pending位图的过程;

5.将所有信号都block

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cassert>

using namespace std;

static void showPending(sigset_t &pending)
{
    for (int sig = 1; sig <= 31; sig++)
    {
        if (sigismember(&pending, sig))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << endl;
}

static void blockSig(int sig)
{
    sigset_t bset;
    sigemptyset(&bset);
    sigaddset(&bset, sig);
    int n = sigprocmask(SIG_BLOCK, &bset, nullptr);
    assert(n == 0);
    (void)n;
}

int main()
{
    for(int sig = 1; sig <= 31; sig++)
    {
        blockSig(sig);
    }

    sigset_t pending;
    while(true)
    {
        sigpending(&pending);
        showPending(pending);
        sleep(1);
    }

    return 0;
}

在这里插入图片描述
当发到9号信号的时候,进程停止,9号信号是不能被屏蔽的;
在这里插入图片描述
跳过9号信号:
在这里插入图片描述
19号也是无法屏蔽
在这里插入图片描述

二、处理信号

1.信号处理的时机

  • 信号产生之后,可能无法被立即处理,要在合适的时候处理;
  • 因为信号的相关数据字段都是在进程PCB内部,这属于内核范畴,进程在运行时会从内核范畴 -> 内核状态 -> 用户态 -> 内核状态 -> 内核范畴;
    在内核态中,从内核态返回用户态的时候,进行信号的检测和处理
  • 当我们进行系统调用的时候,比如缺陷异常等,会进入内核态;int 80是一个系统中断语句,可以陷入内核;
  • 用户态是一个受管控的状态,内核态是一个操作系统执行自己代码的一个状态,具备非常高的优先级;
  • CPU的寄存器是由两套的,一套用户可见,另一套不可见,CPU自用;
  • CR3表示当前CPU的执行权限,1表示内核,3表示用户;
  • 在进程地址空间中,不光有用户地址空间,还有内核地址空间,内核地址空间使用的是内核级的页表,该页表是整个OS只有一份的,能够被所有的进程看到,因此所有进程看到的都是一个操作系统;
    在这里插入图片描述
    当我们进程需要调用系统接口时,就跳转到进程的内核地址空间,根据内核级页表,在内存中找到系统调用的相关方法;
  • 当我们有权限进入内核态时,进程使用的页表就是内核级页表了,就能够访问 OS的方法了,这也就意味着进程进入了内核态,可以处理信号了;

2.信号处理的流程

在这里插入图片描述

  • 注意:
    (1)在第二步时,进程在内核态处理完成系统任务后,会在重回用户态的时候进行信号的检测和处理;
    (2)在第三步检测到信号,并处理时,如果信号的处理方式时系统默认方式,就直接在内核态处理了,然后返回用户态的执行流继续执行;如果信号的处理方式是用户自定义的,就需要返回用户态去执行相应的方法;这时进程的状态时用户态,能够执行自定义信号处理,但是系统不会去在内核态执行用户代码,因为涉及到系统安全问题;
    (3)在第四步返回用户态执行信号处理后,进程会再次进入内核态,从内核态在返回用户态进程中断处继续执行;
    (4)一共四次状态切换;

3.sigaction

在这里插入图片描述

  • 参数:
    signum:信号编号;
    act:信号处理动作;struct sigaction是一个结构体,里面包含用户自定义的信号处理方式的函数指针等数据;
    在这里插入图片描述
    oldact:信号过去的处理方式;
#include<iostream>
#include<signal.h>
#include<unistd.h>

using namespace std;

void handler(int signum)
{
    cout << "获取了一个信号:" << signum << endl;
}

int main()
{
    //内核数据类型,用户栈定义的
    struct sigaction act, oact;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = handler;

    //设置进当前调用进程的PCB中
    sigaction(2, &act, &oact);

    while(true) sleep(1);

    return 0;
}

运行结果:
在这里插入图片描述
捕获2号信号并执行自定义处理方式;

  • 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的的信号屏蔽字,这样就保证了在处理某个信号时,如果该信号再次产生,那么它就会被阻塞到当前信号处理结束为止;如果在调用信号处理函数时,还希望屏蔽除当前信号的其他信号,就可以使用sigaction函数的sa_mask参数,来指定希望额外屏蔽的信号;
#include<iostream>
#include<signal.h>
#include<unistd.h>

using namespace std;


static void showPending(sigset_t &pending)
{
    for (int sig = 1; sig <= 31; sig++)
    {
        if (sigismember(&pending, sig))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << endl;
}

void handler(int signum)
{
    cout << "获取了一个信号:" << signum << endl;
    sigset_t pending;
    int c = 10;
    while(true)
    {
        sigpending(&pending);
        showPending(pending);
        c--;
        if(!c)
        {
            break;
        }
        sleep(1);
    }
}

int main()
{
    //内核数据类型,用户栈定义的
    struct sigaction act, oact;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = handler;

    //设置进当前调用进程的PCB中
    sigaction(2, &act, &oact);

    while(true) sleep(1);

    return 0;
}

运行结果:
在这里插入图片描述
第二次获取二号信号的时候,就进行了屏蔽;

如果需要同时添加对其他信号的屏蔽:

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

using namespace std;


static void showPending(sigset_t &pending)
{
    for (int sig = 1; sig <= 31; sig++)
    {
        if (sigismember(&pending, sig))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << endl;
}

void handler(int signum)
{
    cout << "获取了一个信号:" << signum << endl;
    sigset_t pending;
    int c = 10;
    while(true)
    {
        sigpending(&pending);
        showPending(pending);
        c--;
        if(!c)
        {
            break;
        }
        sleep(1);
    }
}

int main()
{
    //内核数据类型,用户栈定义的
    struct sigaction act, oact;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = handler;

    //同时添加对其他信号的屏蔽
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaddset(&act.sa_mask, 5);
    sigaddset(&act.sa_mask, 6);
    sigaddset(&act.sa_mask, 7);

    //设置进当前调用进程的PCB中
    sigaction(2, &act, &oact);

    while(true) sleep(1);

    return 0;
}

运行结果:
在这里插入图片描述

三、可重入函数

在这里插入图片描述
在main函数调用insert方法时,信号来了,调用handler,handler也去调用insert,那么像这样被多个执行流调用insert就叫做函数重入
在这里插入图片描述
函数重入出问题的叫做不可重入函数;
不出问题的叫做可重入函数;

函数的可重入性是函数的一种特征,我们目前使用的大多数函数,都是不可重入的;

四、volatile关键字

当接收到2号信号时,将flag置1,进程退出;
在这里插入图片描述
运行结果:
在这里插入图片描述
如果我们更改编译选项,让g++对代码作出一定的优化:
在这里插入图片描述
运行结果:
在这里插入图片描述
现在进程就无法退出了,但是flag还是变成了1;

这是因为在优化了代码之后,后面的语句没有更改flag,在后面检测flag的时候,就不访问内存中的flag了,而是检测寄存器edx中的flag;而寄存器中的flag是第一次读取的0,因此进程就不会退出了;
在变量定义的时候加上volatile关键字:
在这里插入图片描述
这个关键字的作用是**保持变量在内存中的可见性;**
运行结果:
在这里插入图片描述
注:优化是在编译时就完成的;

五、SIGCHILD信号

在这里插入图片描述
在这里插入图片描述
如果我们需要等待子进程退出,10个子进程5个退出,后面的信号还需要进行wait检测是否退出;
因为5个进程都发送了sigchild信号,但是OS只能收到一个;
这时主进程只能阻塞等待该子进程退出;
我们也可使用vector保存进程pid,来进行非阻塞遍历所有进程,这样不会被阻塞;
也可以在waitpid时候传入-1, 就可以等待任意一个退出的进程,进程也不会被阻塞;

  • 如果我们不想等待子进程,还想在子进程退出之后,自动释放僵尸子进程:可以设置对SIGCHILD信号的忽略
    在这里插入图片描述
    运行结果:
    子进程退出后自动回收僵尸子进程;
    在这里插入图片描述
    sigchild的默认动作就是忽略,但是为什么要再加一个忽略呢?
    因为这两个忽略时不同等级的,OS的忽略就是默认动作,不会回收子进程,会形成僵尸进程;
    而自己设置的忽略,告诉OS不光要忽略子进程,还要回收资源;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/866016.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

postgresql之对象池(slab)

创建SlabContext 分配对象 创建对象池 分配空间初始化分配的空间将block加入循环双向链表 从对象池中获取对象 从双向循环链表中获取一个block /* grab the block from the freelist (even the new block is there) */ block dlist_head_element(SlabBlock, node,&sla…

【React学习】—类的基本知识(五)

【React学习】—类的基本知识&#xff08;五&#xff09; <script>// 创建一个Person类class Person{//构造器方法constructor(name,age){this.namename;this.ageage;}//一般方法speak(){//speak方法一般放在哪里&#xff1f;类的原型上&#xff0c;供实例使用//通过Pers…

RabbitMQ 79b5ad38df29400fa52ef0085a14b02f

RabbitMQ 一、什么是消息队列 消息队列可以看作是一个存放消息的容器&#xff0c;其中&#xff0c;生产者负责生产数据到消息队列中&#xff0c;而消费者负责消费数据。消息队列是分布式系统中重要的组件&#xff0c;目前使用较多的消息队列有ActiveMQ&#xff0c;RabbitMQ&am…

unity实现角色体力功能【体力条+体力计算】

导读&#xff1a;实现功能 1、角色体力计算 2、角色疲劳动画 3、体力条制作、跟随 默认做好角色的idle/run/walk动画、切换和玩家输入&#xff0c;我使用的是新输入系统&#xff0c;动画时单变量混合树&#xff0c;参数Sports。 【每一部分功能根据自己需求观看哦】 1、角色体…

wsl2安装docker引擎(Install Docker Engine on Debian)

安装 1.卸载旧版本 在安装 Docker 引擎之前&#xff0c;您必须首先确保卸载任何冲突的软件包。 发行版维护者在他们的存储库。必须先卸载这些软件包&#xff0c;然后才能安装 Docker 引擎的正式版本。 要卸载的非官方软件包是&#xff1a; docker.iodocker-composedocker-…

使用gewe框架进行微信群组管理(一)

友情链接&#xff1a;geweapi.com 点击访问即可。 管理员操作 小提示&#xff1a; 添加、删除、转让多个wxid时仅限于添加/删除管理员&#xff0c;1添加 2删除 3转让 请求URL&#xff1a; http://域名地址/api/group/admin 请求方式&#xff1a; POST 请求头&#xff1a…

基本动态规划问题的扩展

基本动态规划问题的扩展 应用动态规划可以有效的解决许多问题&#xff0c;其中有许多问题的数学模型&#xff0c;尤其对一些自从57年就开始研究的基本问题所应用的数学模型&#xff0c;都十分精巧。有关这些问题的解法&#xff0c;我们甚至可以视为标准——也就是最优的解法。…

【LeetCode】870 . 优势洗牌

870 . 优势洗牌 方法&#xff1a;贪心 思路 这道题的思想类似于 “田忌赛马” &#xff0c;把 nums1 当成是田忌的马&#xff0c;nums2 当成是齐威王的马。 讨论田忌的下等马&#xff08;nums1 的最小值&#xff09;&#xff1a; 如果它能比过齐威王的下等马&#xff08;nums…

PHP利用PCRE回溯次数限制绕过某些安全限制实战案例

目录 一、正则表达式概述 有限状态自动机 匹配输入的过程分别是&#xff1a; DFA&#xff08;确定性有限状态自动机&#xff09; NFA&#xff08;非确定性有限状态自动机&#xff09; 二、回溯的过程 三、 PHP 的 pcre.backtrack_limit 限制利用 例题一 回溯绕过步骤 &…

host文件被锁死无法修改怎么办?解锁host文件修改新方法~~

日常操作发现host文件被锁死&#xff0c;host文件左下角出现一个&#x1f512;&#xff0c;无法修改怎么办&#xff1f;别急&#xff0c;最简单的解决方法分享啦&#xff0c;一起来围观吧&#xff01; 应用程序-实用工具中打开终端 输入代码【sudo chflags -hv noschg /etc/hos…

2023年游戏买量能怎么玩?

疫情过后&#xff0c;一地鸡毛。游戏行业的日子也不好过。来看看移动游戏收入&#xff1a;2022年&#xff0c;移动游戏收入达到920亿美元&#xff0c;同比下降6.4%。这告诉我们&#xff0c;2022年对移动游戏市场来说是一个小挫折。 但不管是下挫还是上升&#xff0c;移动游戏市…

软件测试面试夺命连环十七问,你答得上来么?这都不会建议多学!

1. 给你一个网站&#xff0c;该如何测试&#xff1f;&#xff08;探究需求制订计划&#xff09; 首先&#xff0c;查找需求说明、网站设计等相关文档&#xff0c;分析测试需求。 制定测试计划&#xff0c;确定测试范围和测试策略&#xff0c;一般包括以下几个部分&#xff1a…

【Spring MVC】Spring MVC基于注解的程序开发

目录 一、什么是Spring MVC 二、Spring MVC项目的创建和使用 1、实现客户端和服务器端之间的连接 1.1、RequsestMapping注解 1.2、RequestMapper的简单使用 1.3、使用GetMapping和POSTMapping注解来实现HTTP连接 三、获取参数 1、实现获取单个参数 2、实现获取对象 3…

002-Spring boot 自动配置相关分析

目录 自动配置开启自动配置读取配置提前过滤 自动配置 开启自动配置 在Spring 启动类上的 SpringBootApplication 中有 EnableAutoConfiguration 读取配置 Import(AutoConfigurationImportSelector.class) public interface EnableAutoConfiguration {AutoConfigurationEnt…

vxe table: 实现tree表格,并且自定义展示指定行

要求&#xff0c;数据中必须有唯一的id字段&#xff0c;并且row-config.KeyField 要指定这个id字段。否则在自定义展开行时展开不生效。并不影响tree的渲染 数据有两种形式 普通数据结构tree 状结构&#xff0c; 以树状结构为例: 首先我们要将普通结构的数据&#xff0c;按…

Go语言工程实践之测试与Gin项目实践

Go 语言并发编程 及 进阶与依赖管理_软工菜鸡的博客-CSDN博客 03 测试 回归测试一般是QA(质量保证)同学手动通过终端回归一些固定的主流程场景 集成测试是对系统功能维度做测试验证,通过服务暴露的某个接口,进行自动化测试 而单元测试开发阶段&#xff0c;开发者对单独的函数…

企业级帮助中心编写方案怎么写?

在现代商业环境中&#xff0c;为客户提供高效的支持和解决方案至关重要。企业级帮助中心是一个集中管理和呈现常见问题和解答的平台&#xff0c;可以为客户提供快速、便捷的自助帮助。本文将提供一个企业级帮助中心编写方案&#xff0c;旨在帮助企业提供优质的客户支持&#xf…

进程间通信(IPC)的几种方式

进程间通信&#xff08;IPC&#xff09; 1.常见的通信方式2.低级IPC方法文件 3.常用于本机的IPC机制3.1无名管道pipe3.2命名管道FIFO3.3消息队列MessageQueue3.4共享内存SharedMemory3.5信号量Semaphore3.6信号Signal3.7unix域套接字 4.不同计算机上的IPC机制5.IPC机制的数据拷…

数学符号说明——三角等号(≜)

三角等号 &#xff0c;LaTex语法宏 (\triangleq&#xff09;&#xff0c;Unicode(U225C)&#xff0c;又称 "delta equal to(Δ 等)"。可以读作 "等于"、"根据定义 x 等于 y "。 有时候&#xff0c;用在数学(和物理学)的某种定义中。例如&#…