进程信号(Linux)

news2024/11/18 5:52:37

进程信号

  • 信号入门
    • 身边的信号
    • 进程信号
  • 产生信号
    • 终端按键产生信号
    • 调用系统函数向目标进程发信号
      • kill
      • raise
      • abort
    • 硬件异常产生信号
    • 由软件条件产生信号
  • 阻塞信号
    • 信号其他相关常见概念
    • 在内核中的表示
    • sigset_t
    • 信号集操作函数
      • sigprocmask
      • sigpending
  • 捕捉信号
    • 内核如何实现信号的捕捉
    • sigaction

信号入门

信号分为四个阶段:

  1. 预备
  2. 产生
  3. 保存
  4. 处理

身边的信号

用一个简单的栗子来解释信号的四个阶段:
当我们过马路遇到红绿灯时,首先我们是能够识别红绿灯的(色盲除外),识别包含两个重要的因素:认识,并且能够产生对应的行为;可是我们为什么红绿灯呢?一定是有人所教育的,可能是在学校里面所学习的,亦或被家人所教育的,这其实就是信号的预备阶段;变绿灯时,我们不一定要立刻就过马路,如果此时我们有更重要的事情要处理,我们就会选择等待下一次,变灯其实就是产生信号,选择等待下一次就是信号的处理,在信号产生和处理之间还存在着信号的保存,也就是信号需要被记住;信号的处理方式也可以分为三种:默认动作,就是红灯停绿灯行,自定义动作,例如变绿灯之后等上几秒钟再过马路,忽略,不过马路

进程信号

将上面的概念,迁移至进程中
首先,我们有个共识:信号是由操作系统发给进程的;进程认识信号,并且还会做出对应的动作;并不是信号一产生,进程会立刻处理信号,所以进程本身必须有保存信号的能力;进程处理信号的方式也是三种:默认,自定义,忽略
,处理信号也称信号被捕捉

信号集,只学习[1,31]普通信号
在这里插入图片描述

这里再理解一个概念:既然进程能够保存信号,那么应该保存在哪里呢???
其实不难想象,信号是保存在PCB中的,结构体中存在一个属性是用来保存信号的
在这里插入图片描述
比特位的位置,代表信号编号;比特位的内容,代表是否收到信号,0表示没有,1表示有;所以发送信号的本质就是修改 PCB信号位图,由于 PCB是内核维护的数据结构对象,操作系统又是进程的管理者,所以无论发生什么信号,本质都是操作系统向进程发送的信号,操作系统必须提供发送信号和处理信号相关的系统调用

产生信号

终端按键产生信号

举个栗子,观察进程中的信号

int main()
{
    while(true)
    {
        cout<<"我是一个进程"<<endl;
        sleep(2);
    }
}

在这里插入图片描述
热键ctrl+c能够将进程终止,本质是操作系统将其解释为2号信号SIGINT

在这里插入图片描述

介绍一个函数

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  1. signum:信号编号
  2. handler:函数指针,将信号自定义处理

修改上面的栗子

void handler(int signo)
{
    cout<<"进程捕捉到一个信号,信号编号:"<<signo<<endl;
}

int main()
{
    signal(2,handler);
    while(true)
    {
        cout<<"我是一个进程"<<getpid()<<endl;
        sleep(2);
    }
}

在这里插入图片描述

虽然设置了 signal函数,但是进程还是正常打印了四次,因为没有捕捉到信号, handker函数并没有被调用;当输入热键时,信号被捕捉,由于处理方式是自定义的,所以进程没有直接退出

调用系统函数向目标进程发信号

kill

int kill(pid_t pid, int sig);
  1. pid:正在运行的进程的pid
  2. sig:将信号sig发送给目标进程;可以是任意信号

实例如下:
mysignal.cpp

void Usage(const string&proc)
{
    cout<<"\nUsage "<<proc<<" pid signo\n"<<endl;
}
int main(int argc,char*argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    pid_t pid=atoi(argv[1]);
    int signo=atoi(argv[2]);
    int n=kill(pid,signo);
    if(n!=0)
    {
        perror("kill");
    }
    return 0;
}

mytest.cpp

int main()
{
    while(true)
    {
        cout<<"我是一个正在运行的进程,pid: "<<getpid()<<endl;
        sleep(1);
    }
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

19号信号可以将进程停下来

raise

int raise(int sig);

将信号sig直接发送给进程(自己)

int main()
{
    int cnt=0;
    while(cnt<=10)
    {
        printf("cnt:%d,pid:%d\n",cnt++,getpid());
        sleep(1);
        if(cnt>=5)
        {
            raise(9);
        }
    }
    return 0;
}

在这里插入图片描述

在这里插入图片描述

9号信号直接将进程杀死

abort

void abort(void);

给进程(自己)发送指定信号,终止进程

int main()
{
    int cnt=0;
    while(cnt<=10)
    {
        printf("cnt:%d,pid:%d\n",cnt++,getpid());
        sleep(1);
        if(cnt>=5)
        {
            abort();
        }
    }
    return 0;
}

在这里插入图片描述

在这里插入图片描述

发送的指定信号其实是6号信号

硬件异常产生信号

信号的产生不一定是用户显示地发送

观察下列代码:

int main()
{
    while(true)
    {
        cout<<"我正在运行中..."<<endl;
        sleep(1);
        int a=10;
        a/=0;
    }
}

在这里插入图片描述

程序运行的结果是溢出,为什么进行a/=0会终止进程,进程终止是收到操作系统发送的信号,具体是怎么做的呢?
在这里插入图片描述
在CPU中进行计算,将计算结果存放到寄存器中,由于a/=0计算的结果溢出,此时状态寄存器将溢出标记位记为1,由于操作系统是管理软硬件资源的,CPU运算异常会被其得知,然后将8号信号发送给进程,让进程终止

验证如下:

void catchSIG(int signo)
{
    cout<<"获取一个信号,信号编号是: "<<signo<<endl;
}

int main()
{
    signal(SIGFPE,catchSIG);
    while(true)
    {
        cout<<"我正在运行中..."<<endl;
        sleep(1);
        int a=10;
        a/=0;
    }
}

在这里插入图片描述

信号被捕获了,可是进程没有直接退出;收到信号,不一定会让进程退出,既然进程没有退出,就还有可能再次被获取

CPU内部地寄存器只有一份,寄存器中的内存保存地是进程的上下文;当进程被切换时,状态寄存器就会被保存和恢复,每一次的恢复,操作系统都会识别到CPU内部的状态寄存器的溢出标志位是1,便会向进程发送8号信号

由软件条件产生信号

在上一章的管道中,如果关闭管道一端读端,进程会收到13信号,结束进程,这个信号是在软件条件下产生的;这里介绍另一个在软件条件下产生的信号

unsigned int alarm(unsigned int seconds);

调用alarm函数可以设定一个闹钟,也就是告知内核在seconds秒之后给当前进程发送SIGALRM信号,默认处理是终止当前进程

代码实现:

int main()
{
    int cnt=0;
    alarm(1);
    while(true)
    {
        cout<<"cnt: "<<cnt++<<endl;
    }
}

在这里插入图片描述

为什么说alarm函数就是软件条件下的产生的信息呢???

闹钟其实就是由软件实现的,任何一个进程,都可以通过alarm系统调用在内核中设置闹钟,操作系统内可能会存在着很多的闹钟,操作系统为了管理这些闹钟,需要先描述再组织

先描述
在这里插入图片描述

再描述
在这里插入图片描述

操作系统会周期性地检查这些闹钟;超时时,操作系统会发送信号SIGALARM给进程,也就是调用alarm.p,进而终止进程

最后还有一点,进程退出时的核心转储
观察代码

int main()
{
    int a[10];
    a[1000]=10;
    return 0;
}

在这里插入图片描述
在这里插入图片描述

程序发生了段错误,并且进程是 core退出,在上面还有信号是 term退出,这种退出称为正常退出; core退出时,程序还会做一些其他操作

云服务器上 core退出时,看不到明显现象,默认关闭了 core file
在这里插入图片描述

自己可以打开云服务器中的 core file

在这里插入图片描述

再次运行程序

在这里插入图片描述

段错误后面出现了一个新的内容core dumped也就是核心存储:当进程出现异常时,操作系统会将进程在对应时刻有效的数据转储到磁盘中,运行之后还多出来一个文件core.2545,后面的编号就是引起问题进程的pid

核心转储存在的意义是为了调试,更方便用户检查进程崩溃的原因
实操一下:
在这里插入图片描述

总结

  1. 以上所有的信号产生都是由操作系统来执行,因为操作系统是管理软硬件资源的
  2. 信号的处理并不是立刻,而是在适合的时候
  3. 信号没有被立刻处理,信号会被暂时存放在PCB中
  4. 进程在收到信号之前,就已经知道该如何处理
  5. 操作系统向进程发送信号,其实就是修改PCB中的位图

阻塞信号

信号其他相关常见概念

  1. 实际执行信号的处理动作称为信号递达
  2. 信号从产生到递达之间的状态称为信号未决
  3. 进程可以选择阻塞某个信号
  4. 被阻塞的信号产生时将保存在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作
  5. 阻塞和忽略是不同的,信号只要被阻塞就不会递达,忽略本质上就是递达之后选择的一种处理动作

在内核中的表示

在这里插入图片描述

每个进程都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作;信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志;如果一个信号没有被产生,并不妨碍它可以先被阻塞

sigset_t

每个信号只有一个比特位,非0即1,不记录该信号产生了多少次,阻塞标志亦是于此;未决和阻塞都可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,此类型可以表示每个信号的有效或无效状态;阻塞信号也称当前进程的信号屏蔽字,屏蔽是阻塞而不是忽略

信号集操作函数

sigset_t类型对于每种信号用一个比特位表示有效或无效,至于类型内部如何存储这些比特位则依赖于系统实现

int sigemptyset(sigset_t *set);
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);
  1. 函数 sigempty初始化 set所指向的信号集,使其中所有信号对应的比特位清零,表示该信号集不包括任何有效信号
  2. 函数 sigfillset初始化 set所指向的信号集,使其中所有信号对应的比特位置为1,表示该信号集的有效信号包括系统所支持的所有信号
  3. 函数 sigaddset将信号添加到信号集中;函数 sigdelset将信号从信号集中删除;函数 sigismember判断某种信号是否在信号集中

sigprocmask

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

如果oldset是非空指针,则读取进程当前的信号屏蔽字通过oldset参传出,如果set是非空指针,则更改进程当前的信号屏蔽字,参数how指示如何更改;如果oldsetset都是非空指针,则将原来的信号屏蔽字备份到oldset中,然后根据sethow参数更改当前的信号屏蔽字

SIG_BLOCKset包含了所有待添加到信号集中的信号
SIG_UNBLOCKset包含了所有待从信号集中删除的信号
SIG_SETMASK设置当前信号屏蔽字为set所指向的值

sigpending

int sigpending(sigset_t *set);

读取当前进程的未决信号集,通过set参数传出

默认情况下所有信号都不是阻塞的,如果信号被屏蔽,则该信号不会被递达
代码实现:屏蔽信号2

#include<iostream>
#include<signal.h>
#include<unistd.h>
#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31
using namespace std;
void show_pending(const sigset_t& pending)
{
    for(int signo=MAX_SIGNUM;signo>=1;signo--)
    {
        if(sigismember(&pending,signo))
        {
            cout<<"1";
        }
        cout<<"0";
    }
    cout<<"\n";
}

int main()
{
    //1.屏蔽指定信号
    sigset_t block,oldblock,pending;
    //1.1初始化
    sigemptyset(&block);
    sigemptyset(&oldblock);
    sigemptyset(&pending);
    //1.2添加要屏蔽的信息
    sigaddset(&block,BLOCK_SIGNAL);
    //1.3开始屏蔽
    sigprocmask(SIG_SETMASK,&block,&oldblock);
    
    //2.遍历打印pending信号集
    while(true)
    {
        //2.1初始化
        sigemptyset(&pending);
        //2.2获取
        sigpending(&pending);
        //2.3打印
        show_pending(pending);
        //间隔一秒
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

捕捉信号

内核如何实现信号的捕捉

操作系统中进程存在两种状态:用户态,内核态;用户态一般会访问操作系统的资源和硬件资源,为达这一目的,必须通过系统提供的系统调用接口,而且执行系统调用的身份必须是内核,为什么用户可以访问系统调用接口呢???

在CPU中存在着许多的寄存器分为:可见寄存器,不可见寄存器,只要是和进程强相关的都是保存着进程的上下文的数据;名为CR3的寄存器保存着当前进程的运行级别:0表示内核态,3表示用户态,在系统调用接口的起始位置,存在着int 80汇编,会将用户态修改为内核态,从而可以以内核态的身份访问系统调用接口

进程以内核身份访问系统调用接口的具体过程又是怎么样的呢???
在之前进程空间中学习过,进程空间包括用户空间和内核空间,系统调用接口就与这内核空间有关:每个进程都有自己的进程空间,用户空间独享,内核空间只有一份,也就是说所有进程共享同一份内核空间;当进程访问接口时,只需要在进程空间上进行跳转即可,就类似与动态库加载到进程空间

图解:

在这里插入图片描述

当开机时,操作系统会从磁盘加载到内存中的内核区中,当进程以内核态身份访问系统调用时,会在进程空间中跳转到内核空间通过内核级页表映射到内存中操作系统完成相应的调用,完毕之后再跳转回用户空间

信号在产生时,并不会被立刻处理,而是在合适的时间被操作系统处理,这个合适的时间就是从内核态返回用户态时;所以,进程在进程切换或者系统调用时先进入内核态,在内核态中进行信号检测,也就是进程中的两个标志位(pending/block)和函数指针(handler*):如果信号未决且未被阻塞,查找函数指针是否有对应的自定义处理方法,若有,将进程内核态身份修改为用户身份完成对应的处理方法,再还原为内核身份,完成剩余的系统调用,待系统调用结束后,最后将身份修改为用户态继续执行后续的代码

图解:

在这里插入图片描述

需要注意的是:不能以内核态的身份执行用户态的代码,因为操作系统不相信任何人,以免有人进行恶意破坏

sigaction

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
  1. signum:待捕获的信号编号
  2. act:结构体指针
    在这里插入图片描述
    其中包含,处理方法sa_handler和信号集sa_mask

举个栗子,通过此函数捕获2号信号,捕获信号后休息20秒,多次向进程发送2号信号,观察进程运行结果

void Count(int cnt)
{
    while(cnt)
    {
        printf("cnt:%2d\r",cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
    printf("\n");
}

void handler(int signo)
{
    cout<<"get a signo"<<signo<<"正在处理..."<<endl;
    Count(20);
}

int main()
{
    struct sigaction act,oldact;
    act.sa_handler=handler;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,&oldact);
    while(true)
    sleep(1);
    return 0;
}

在这里插入图片描述
在这里插入图片描述

虽然向进程发送多次同一种信号,但是进程只捕获了两次,因为当进程正在递达某一个信号时,将同种类型的信号是无法被抵达的,系统会自动将当前信号添加到屏蔽字中,将pending位图中该信号所在位置修改为0,再次发送同一信号,会将pending位图中该信号所在位置修改为1;当进程完成信号的递达时,系统会自动解除对该信号的屏蔽,所以系统会立即递达pending位图中的信号,也就是捕获第二次信号

当我们正在处理2号信号时,还想屏蔽3号信号,此时只需要将3号信号加入sa_mask信号集即可

void Count(int cnt)
{
    while(cnt)
    {
        printf("cnt:%2d\r",cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
    printf("\n");
}

void handler(int signo)
{
    cout<<"get a signo"<<signo<<"正在处理..."<<endl;
    Count(20);
}

int main()
{
    struct sigaction act,oldact;
    act.sa_handler=handler;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    //添加3号信号
    sigaddset(&act.sa_mask,3);
    sigaction(SIGINT,&act,&oldact);
    while(true)
    sleep(1);
    return 0;
}

在这里插入图片描述
在这里插入图片描述

和上面有所不同的是,这里的进程在最后直接退出了,其实是因为,在2号信号被接触屏蔽后,再次执行2号信号,最后执行3号信号结束进程

进程处理信号的原则是串行处理同类型的信号,不允许递归

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

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

相关文章

亚马逊云科技工业数据湖x创新应用,助您释放全新生产力

数字化浪潮蓬勃发展&#xff0c;制造行业数字化转型热度迭起&#xff0c;根据麦肯锡面向全球400多家制造型企业的调研表明&#xff0c;几乎所有细分行业都在大力推进数字化转型&#xff0c;高达94%的受访者都称&#xff0c;数字化转型是他们危机期间维持正常运营的关键。 数字化…

shell脚本之数组,冒泡排序算法

目录 一、数组 1.定义数组 2. 数组中数据类型 2.1数值类型 2.2字符类型 二、数组的用法 1. 输出数组中的值 2. 统计数组参数个数 ​编辑 3.查看数组下标列表 4.分割字符串 5.替换数组中的字符 6.删除数组 三、数组追加元素 1.方法1示例 2.方法2示例 3.方法3示例 …

SpringCloud Alibaba详解

目录 微服务架构概念 服务治理 服务调用 服务网关 服务容错 链路追踪 SpringcloudAlibaba组件 Nacos 负载均衡 Ribbon Fegin Sentinel 高并发测试 容错方案 Sentinel入门 Feign整合Sentinel 微服务架构概念 服务治理 服务治理就是进行服务的自动化管理&#xf…

MYSQL的主键和外键,内连接和外连接,关联子查询

目录 友情提醒第一章&#xff1a;MYSQL数据库多表主键和外键1&#xff09;外键介绍&#xff08;FOREIGN KEY&#xff09;2&#xff09;外键约束作用2&#xff09;三种情况下添加外键约束①一对一关系②一对多关系多对多关系 4&#xff09;删除外键约束 第二章&#xff1a;MYSQL…

scitb5函数1.4版本(交互效应函数P for interaction)发布----用于一键生成交互效应表

在SCI文章中&#xff0c;交互效应表格&#xff08;通常是表五&#xff09;能为文章锦上添花&#xff0c;增加文章的信服力&#xff0c;增加结果的可信程度&#xff0c;还能进行数据挖掘。 交互效应表我在既往文章《R语言手把手教你制作一个交互效应表》已经介绍怎么制作了&…

提效新纪元-组件化开发在转转App中的应用-后端篇

1 前言 组件化开发是一种利用可重用的软件构件来设计和开发计算机系统的过程。借助组件化开发可以实现最小化、高效交付。 平台基础体验部将业务逻辑抽象为组件&#xff0c;通过组合组件快速构建商品Feed流&#xff0c;研发效率整体提升2倍。组件化开发不仅带来效率的提升&am…

【AI大模型智慧办公】教你用讯飞星火大模型5分钟写一个转正述职ppt

文章目录 前言SparkDesk讯飞星火认知大模型简介利用讯飞星火写一个转正述职ppt1.告诉讯飞星火我想写一篇转正述职ppt2.利用MindShow一键生成ppt 申请体验写在最后 前言 随着ChatGPT迅速走红,国内各大企业纷纷发力认知大模型领域。经过一段时间的酝酿,讯飞“星火认知大模型”于…

解决matplotlib画图去除非常多的白色边框

文章目录 解决matplotlib画图去除非常多的白色边框本文说明为什么matplotlib画图会默认有很多白色边框&#xff1f;绘图默认绘图去除白边方法一&#xff0c;修改保存图像的代码取消白边略微增加白边 去除白边方法二&#xff0c;修改显示图像的代码 总结 解决matplotlib画图去除…

Maven构建生命周期

目录 Default (Build) 生命周期 命令行调用 Site 生命周期 如何清除本地 Maven 仓库并重新构建项目 Maven 构建配置文件 mvn dependency:purge-local-repository 这个命令是干什么的&#xff1f; mvn clean install这个命令是干什么的&#xff1f; 配置文件激活 1、配…

自从外包三年半,程序员人废了一半

如果不是女朋友和我提分手&#xff0c;我估计现在还没醒悟。大专生&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了3年多的CRUD&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企…

HBase入门 修改表字段类型(十一)

一、查询看 自己表的DATA_TYPE select TENANT_ID,TABLE_SCHEM,TABLE_NAME,COLUMN_NAME,COLUMN_FAMILY,DATA_TYPE,TYPE_NAME,COLUMN_SIZE,DECIMAL_DIGITS from system.catalog where TABLE_NAME表名2、修改为varchar类型 upsert into SYSTEM.CATALOG (TENANT_ID,TABLE_SCHEM,TA…

如何高清视频录制?您只需要这样操作!

案例&#xff1a;如何录制画质高清的视频&#xff1f; 【我录制了一个视频课程&#xff0c;上传到网上&#xff0c;但是我录制的视频画质不好&#xff0c;影响观感。有没有支持高清录制的录屏工具&#xff1f;有没有小伙伴可以推荐一下&#xff01;在线等&#xff01;】 无论…

ChatGpt与AI交谈的未来到来,你准备好了吗?

ChatGpt已经成为了人工智能领域中的一颗明珠&#xff0c;它可以根据用户提出的问题进行分析和回答&#xff0c;帮助用户解决问题和获取信息。而未来&#xff0c;随着人工智能技术的进一步发展&#xff0c;ChatGpt与AI交谈将成为一种更加普遍和重要的交流方式。你准备好了吗&…

7 文件操作、单元测试、goroutine【Go语言教程】

7 文件操作、单元测试、goroutine【Go语言教程】 1 文件操作 1.1 介绍 os.File 封装所有文件相关操作&#xff0c;File 是一个结构体 常用方法&#xff1a; 打开文件 关闭文件 package mainimport ("fmt""os" )func main(){//打开文件//file又叫做&…

VPGTrans: 10%的成本定制你自己的类GPT-4多模态大模型

作者 | 张傲 最近的多模态&#xff08;对话&#xff09;大模型将基于文本的ChatGPT的强大能力扩展到了多模态输入&#xff0c;实现强大的多模态语义理解&#xff0c;比如GPT-4、BLIP-2、Flamingo等。但咱们普通玩家训练一个多模态GPT代价非常昂贵。来自于新加坡国立大学和清华大…

基于SSM框架的核酸检测管理系统

基于SSM框架的核酸检测管理系统 快速查看 基于SSM框架的核酸检测管理系统功能需求开发工具模块相关技术系统相关图片 功能需求 用户模块&#xff1a; 注册功能&#xff1a;普通用户可以访问本系统进行账户注册&#xff0c;个人资料&#xff1a;登录系统对自己的个人资料&…

Parker派克伺服电机有哪些优势特点?如何选型?

一、什么是伺服电机&#xff1f; 伺服电机是一种可以通过控制器精确地控制位置、速度和加速度的电机&#xff0c;主要由电机、编码器和控制器三部分组成&#xff0c;具有高转矩、高精度、快速响应和低转速稳定特性&#xff0c;能够在负载扰动、电压变化及机械特性变化下保持较…

springboot整合邮箱功能二(普通邮件, html邮件, thymleaf邮件)

【SpringBoot整合Email发送邮件】_ζั͡ ั͡空 ั͡ ั͡白&#xfffd;的博客-CSDN博客 https://www.cnblogs.com/erlou96/p/16878192.html#_label1_5 1. 准备工作 1.1 qq邮箱设置 本文默认使用qq邮箱来发送邮件,然后使用一个在线临时邮箱来接收邮件。为了让程序能够通过…

srs one2one,one2many通话环境搭建

一、简介 二、go环境配置 三、srs编译配置 四、信令服务器编译 4.1 signaling8 4.2 web服务器 五、测试 六、附录 官⽅⽂档参考地址&#xff1a;https://github.com/ossrs/srs/wiki/v4_CN_WebRTC#sfu-one-to-one 一、简介 srs的webrtc能力和两个信令服务器不管是逻辑上还是代码…

Linux 进程基础

目录 1、进程的概念 2、进程与线程的区别、进程与程序的区别 2.1 进程与线程的区别 2.2 进程与程序的区别 3、进程相关 shell 命令 3.1 ps 3.3.1 参数说明 3.3.2 结果说明 3.2 pidof 3.3 pstree 3.4 top 3.5 kill 4、进程相关函数 4.1 fork 4.1.1 fork的函数原型…