Day04 Liunx高级系统设计4-信号

进程间通讯

引入

如何将 A 进程中的数据传入 B 进程呢 ?
我们要使用进程间通讯

概述

中文名 : 进程间通讯
英文名 :IPC
英文全称 :Inter Processes Communication
作用:
数据传输:一个进程需要将他的数据发送给另一个进程】
资源共享:多个进程可以共享同一个资源
通知事件:一个时间需要给另一个进程或另一个组发送信号,告诉它发生了什么事
进程控制:有些进程希望完全控制另一个进程的执行(如debug),此时控制进程希望拦截另一个进程的所有操作,并能够及时知道它的改变状态。
发展史:
最初的 UNIX 进程间通信
SYSTEM V 进程间通信
POSIX 进程间通信 (POSIX:Portable Operating System interface 可移植操作系
统接口)
Socket 进程间通信
Linux 把优势都继承了下来并形成了自己的 IPC
Linux 进程间通信机制

分类

1.信号量

2.管道

3.消息队列

4.socket(套接字)

5.内存共享

信号

概述

信号是Linux进程间通信的最古老方式

特点

        简单

        不能携带大量信息

        满足某个特设条件才发出。

注意 :
         信号是一种异步通信方式。
        信号是软件中断,它是在软件层次上对中断机制的一种模拟。
        信号可以导 致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某
一个突发事件。
        进程不必等待信号的到达,进程也不知道信号什么时候到达。
        信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件。
名词解释
同步 : 事件、操作或进程是有序的 , 一个操作必须在另一个操作完成后开始执行。
异步 : 事件、操作或进程是独立的 , 可以在不等待其他操作完成的情况下开始执行
中断机制 : 是现代计算机系统中的基本机制之一,完成了对计算机各个事件(如时钟、
键盘等)响应工作
硬件中断 : 是由硬件设备触发的中断,如时钟中断、串口接收中断、外部中断等。当硬件设备有数据或事件需要处理时,会向CPU 发送一个中断请求, CPU 在收到中断请求后,会立即暂停当前正在执行的任务,进入中断处理程序中处理中断请求。硬件中断具有实时性强、可靠性高、处理速度快等特点。
软件中断 : 是由软件程序触发的中断,如系统调用、软中断、异常等。软件中断不是由硬件设备触发的,而是由软件程序主动发起的,一般用于系统调用、进程切换、异常处理等任务。软件中断需要在程序中进行调用,其响应速度和实时性相对较差,但是具有灵活性和可控性高的特点。

完整的信号周期

信号的注册

信号的产生

信号的处理函数

信号的注销

注意:这里的信号的注册,产生,注销是信号的内部机制,不是信号的函数实现

信号的编号

每个信号的名字都以字符 SIG 开头。
每个信号和一个数字编码相对应,在头文件 signum.h 中,这些信号都被定义为 正整
数。
信号名定义路径: /usr/include/i386-linux-gnu/bits/signum.h
Linux 下,要想查看这些信号和编码的对应关系,可使用命令: kill -l
不存在编号为 0 的信号。
1-31 号信号称之为常规信号 ( 也叫普通信号 , 标准信号 )
34-64号信号称为实时信号(自定义信号)
通过 man 7 signal 查看信号帮助文档
注意:9)SIGKILL和19)SIGSTOP信号不允许忽略和捕捉,只能执行默认操作。(重要)
SIGQUIT信号使用 ctrl + ‘\’来触发

信号的产生

1, 当用户按某些终端键时,将产生信号。 终端上按 “Ctrl+c” 组合键通常产生中断信号 SIGINT 终端上按 “Ctrl+\” 键通常产生中断信号 SIGQUIT 终端上按 “Ctrl+z” 键通常产生中断信号 SIGSTOP 等。
2, 硬件异常将产生信号。 除数为 0 ,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。
 
3, 软件异常将产生信号。 当检测到某种软件条件已发生 ( 如:定时器 alarm) ,并将其通知有关进程时,产生信号。
4, 调用系统函数 ( 如: kill raise abort) 将发送信号。
注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。
5, 运行 kill/killall 命令将发送信号。 此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程。

kill函数(他杀)

作用 : 给指定进程发送指定信号 ( 不一定杀死 ), 他杀
语法
所需头
#include <sys/types.h>
#include <signal.h>
函数
int kill(pid_t pid, int sig);
参数 :
pid : 取值有 4 种情况 :
pid > 0: 将信号传送给进程 ID 为 pid 的进程。
pid = 0 : 将信号传送给 当前进程所在进程组中的所有进程。
pid = -1 : 将信号传送给 系统内所有的进程。
pid < -1 : 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
sig : 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill - l("l" 为字母 ) 进行相应查看。不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
返回值 :
成功: 0
失败: -1
示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    int pid = fork();
    if(pid == 0)
    {
        //进入子进程
        while(1)
        {
            printf("子进程%u在玩游戏\n",getpid());
            sleep(1);
        }
        _exit(-1);
    }
    else if(pid > 0)
    {
        //进入父进程
        printf("给你三秒钟去写作业\n");
        sleep(3);
        kill(pid,SIGKILL);
        wait(NULL);
        printf("%d被%d干掉了\n",pid,getpid());
    }
    return 0;
}

raise函数(自杀)

作用:给当前进程自己发指定信号,等价于kill(getpid(),sig),自杀

语法:

所需头:

        #include <signal.h>

函数:

        int raise(int sig);

参数:

        sig:信号编号

返回值:

        成功:0

        失败:非0

示例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    int i = 0;
    while (1)
    {
        sleep(1);
        printf("好无聊,想去死%d\n",i);
        if(i == 3)
        {
            printf("死给你看\n");
            raise(SIGKILL);
        }
        i++;
    }
    wait(NULL);
    return 0;
}

abort函数(自杀)

作用:给自己发送异常终止信号(6)SIGABRT,并产生core文件,等价于kill(getpid(),sig),自杀

core 文件 ( 了解 )
core 就是内核
core 文件会包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息还有各种
函数调用堆栈信息等,我们可以理解为是程序工作当前状态存储生成第一个文件,许多
的程序出错的时候都会产生一个 core 文件,通过工具分析这个文件,我们可以定位到程
序异常退出的时候对应的堆栈调用等信息,找出问题所在并进行及时解决。
语法:
头文件:
        #include <signal.h>
函数:
        void abort(void)
注意:
        1. 使用 abort 函数结束进程 , 进程结束前会刷新缓冲区并关闭所有的文件描述符( 0/1/2
        
        2,即使 SIGABRT 信号被加入阻塞集,一旦进程调用了 abort 函数,进程也还是会被终止
示例:
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    char myfo[3] = {"我要发信号了","我真的要发信号了","拜拜了您呐"};
    int i = 0;
    while (1)
    {
        char *p = myfo[i];
        printf("%s\n",p);
        sleep(1);
        i++;
        if(i == 3)
        abort();
    }
    
    return 0;
}

alarm函数(自杀)

作用:

设置定时器 ( 闹钟 ) 。在指定时间后 ( 单位秒 ) ,内核会给当前进程发送 14 ) SIGALRM 信号。进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个定时器。
取消定时器 alarm(0) ,返回旧闹钟余下秒数。
函数:
所需头文件:
        #include <unistd.h>
函数:
        unsigned int alarm(unsigned int seconds);
参数:
        seconds  单位:秒
返回值:
        若以前没有设置过定时器,或设置的定时器已超时,返回0;
        否则返回定时器剩余的秒数,并重新设定定时器。
注意:
定时,与进程状态无关 ( 即自然定时法 )! 就绪、运行、挂起 ( 阻塞、暂停 ) 、终止、僵 …… 无论进程处于何种状态, alarm 都计时
不阻塞当前进程
示例;
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/time.h>
int main(int argc, char const *argv[])
{
    int seconds = 0;
    seconds = alarm(5);
    printf("计时器开始执行,seconds=%d\n",seconds);
    sleep(3);
    seconds = alarm(5);
    printf("计时器开始执行seconds=%d\n",seconds);
    while(1);
    return 0;
}

setitimer函数(计时器)

作用:

设置定时器(闹钟),可代替 alarm 函数。精度微秒 us,可以实现周期定时。

语法
所需头文件:
        #include <sys/time.h>
函数:
        
        int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
参数 :
        which:指定定时器类型,可以是以下三个值之一:
        a) 自然定时: ITIMER_REAL → 14 SIGALRM 计算自然时间
        
        b) 虚拟空间计时 ( 用户空间 ) ITIMER_VIRTUAL → 26 SIGVTALRM 只计算进程占用 cpu 的时间
        c) 运行时计时 ( 用户 + 内核 ) ITIMER_PROF → 27 SIGPROF 计算占用 cpu及执行系统调用的时间
        new_value:一个指向 struct itimerval 结构体的指针,用于指定新的定时器值(启动时间和间隔时间)。
        old_value:一个指向 struct itimerval 结构体的指针,用于获取旧的定时器值,即之前设置的定时器值。如果不需要获取旧的定时器值,则可以传入NULL
struct itimerval 结构体
struct itimerval {
struct timerval it_interval; // 间隔时间
struct timerval it_value; // 延迟时间
};
struct timerval 结果体
struct timeval {
long tv_sec; //
long tv_usec; // 微秒
};
示例:
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
void fun()
{
    printf("文件已被处理\n");
}
int main(int argc, char const *argv[])
{
    struct itimerval new_a;
    new_a.it_value.tv_sec = 5;
    new_a.it_value.tv_usec = 0;
    new_a.it_interval.tv_sec = 1;
    new_a.it_interval.tv_usec = 0;
    signal(SIGALRM,fun);
    setitimer(ITIMER_REAL,&new_a,NULL);
    while(1);
    return 0;
}

pause函数

作用:

将调用进程挂起直至捕捉到信号为止。这个函数通常用于判断信号是否已到

语法:

头文件:

        #include <unistd.h>

函数:

        int pause(void)

返回值:

        知道捕获到信号,pause函数才返回-1,且errno被设置成EINTR

示例:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void fun()
{
    printf("进程已被执行\n");
}
int main(int argc, char const *argv[])
{
    printf("进程一开始,当前进程是%d\n",getpid());
    signal(SIGALRM,fun);
    alarm(3);
    int x = pause();
    printf("文件结束%d\n",x);
    return 0;
}

信号处理

方式

一个进程收到一个信号的时候,可以用如下方法进行处理:
1 SIG_DFL 执行系统默认动作 对大多数信号来说,系统默认动作是用来终止该进程。
2 SIG_IGN 忽略此信号 ( 丢弃 ) 接收到此信号后没有任何动作。
3 )信号处理函数名 : 执行自定义信号处理函数 ( 捕获 ) 用用户定义的信号处理函数处理
该信号。
【注意】: SIGKILL SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了一种
使进程终止的可靠方法
捕捉信号并且信号信号的处理方式有两个函数 ,signal sigaction
signal 函数
作用:
注册信号处理函数(不可用于 SIGKILL SIGSTOP 信号),即确定收到信号后处理函
数的入口地址。此函数不会阻塞
函数
所需头文件
        #include <signal.h>
函数 :
// 定义的一个函数指针
        typedef void(*sighandler_t)(int);
//signal 函数
        sighandler_t signal(int signum, sighandler_t handler);
参数 :
        signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过
命令 kill - l("l" 为字母 ) 进行相应查看。
handler : 取值有 3 种情况:
        SIG_IGN:忽略该信号
        SIG_DFL:执行系统默认动作
        信号处理函数名:自定义信号处理函数
返回值 :
        成功:第一次返回 NULL ,下一次返回此信号上一次注册的信号处理函数的地址。
        如果需要使用此返回值,必须在前面先声明此函数指针的类型。
        失败:返回 SIG_ERR
示例:
示例 : 自定义 ctrl+c 信号处理 1
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void myfun()
{
    printf("自定义函数\n");
    _exit(0);
}
int main(int argc, char const *argv[])
{
    signal(SIGINT,myfun);
    while(1);

    return 0;
}

示例 : 自定义 ctrl+c 信号处理 2
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
char *p;
void fun(int sigcode)
{
    if(p != NULL)
    {
        free(p);
        
    }
    printf("p空间被释放了\n");
    _exit(0);
}
int main(int argc, char const *argv[])
{
    p =malloc(50);
    strcpy(p,"helloSIG");
    signal(SIGINT,fun);
    while(1)
    {
        printf("%s\n",p);
        sleep(1);

    }
    return 0;
}

注意
signal 函数由 ANSI 定义 , 由于历史原因在不同版本的 Unix 和不同版本的 Linux
可能有不同的行为。因此应该尽量避免使用它 , 取而代之使用 sigaction 函数
sigaction 函数
作用 : 检查或修改指定信号的设置(或同时执行这两种操作)。
函数
所需头文件 :
#include <signal.h>
函数 :
int sigaction(int signum, const struct sigaction *act, struct sigaction* oldact);
参数 :
        signum:要操作的信号。
        act: 要设置的对信号的新处理方式(传入参数)。
        oldact:原来对信号的处理方式(传出参数)。
注意 :
如果 act 指针非空 , 则要改变指定信号的处理方式 ( 设置 ), 如果 oldact 指针非空 , 则系统将此前指定信号的处理方式存入oldact
返回值 :
        成功:0
        失败:-1
struct sigaction:结构体

struct sigaction

{

    void (*sa_handler)(int);                        // 旧的信号处理函数指针

    void (*sa_sigaction)(int, siginfo_t *, void *); // 新的信号处理函数指针

    sigset_t sa_mask;                               // 信号阻塞集

    int sa_flags;                                   // 信号处理的方式

    void (*sa_restorer)(void);                      // 已弃用

};

/*

1,sa_handler,sa_sigaction:信号处理函数指针,和signal()里的函数指针用法

一样,应根据情况给 sa_sigaction、sa_handler 两者之一赋值,其取值如下:

a SIGIGN:忽略该信号

b SIGDFL:执行系统默认动作

c 处理函数名:自定义信号处理函数

2,sa_mask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号。

3,sa_flags:用于指定信号处理的行为,通常设置为 0,表使用默认属性。它可

以是以下值的“按位或”组合:

SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)

SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD

信号。

SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这

时子进程如果退出也不会成为僵尸进程。

SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这

个信号。

SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理

函数

void(*sa_sigaction)(int signum, siginfo_t *info, void *context);

参数说明:

signum:信号的编号。

info:记录信号发送进程信息的结构体。

context:可以赋给指向ucontext_t类型的一个对象的指针,以引用在传递信

号时被中断的接收进程或线程的上下文。

*/

示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
void fun(int sigcode)
{
    printf("ctrl+c的信号编码是:%d\n",sigcode);
    _exit(-1);
}
int main(int argc, char const *argv[])
{
    struct sigaction cat;
    cat.sa_handler = fun;
    sigaction(SIGINT,&cat,NULL);
    while(1);
    return 0;
}

示例2::发出定义信号

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
void fun(int sigcode)
{
    printf("SIGUSR1得信号编号是:%d\n",sigcode);
    _exit(-1);
}
int main(int argc, char const *argv[])
{
    struct sigaction cat;
    cat.sa_handler = fun;
    sigaction(SIGUSR1,&cat,NULL);
    kill(getpid(),SIGUSR1);
    while(1);
    return 0;
}

可重入函数

概念
函数可以同时被很多进程调用,并且保证每个模块都能得到期望的结果
注意
1 、不使用或返回 静态的数据、全局变量(除非用信号量互斥)。
2 、不调用动态内存分配、释放的函数。
3 、不调用任何不可重入的函数(如标准 I/O 函数)
常见的可重入函数列表
示例:
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void show(int pid)
{
    for(int i = 0;i < 3;i++)
    {
        char buf[50] = {0};
        sprintf(buf,"进程%d被%d次执行\n",pid,i,"\r\n");
        write(1,buf,sizeof(buf));
        sleep(1);
    }
}
int main(int argc, char const *argv[])
{
    for (int i = 0; i < 5; i++)
    {
        int pid = fork();
        if(pid == 0)
        {
            printf(getpid());
            _exit(-1);
        }
        else if(pid > 0)
        {
            printf("子进程%d被创建\n",pid);
            _exit(-1);
        }
    }
    while(1)
    {
        int id = waitpid(-1,NULL,WNOHANG);
        if(id > 0)
        {
            printf("子进程在被回收\n",id);
            sleep(1);
        }
        else if(id == 0)
        {
            break;
        }
    }
    return 0;
}

信号集

概述

PCB 中有两个非常重要的信号集。一个称之为 阻塞信号集 ,另一个称之为 未决信号集” 。 这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对其进行位操作。而需自定义另外一个集合,借助信号集操作函数来对 PCB 中的这两个信号集进行修改
信号阻塞集:将某些信号加入 集合,对他们设置屏蔽,当屏蔽 x 信号后,再收到该信号,该信号的处理将暂缓。
未决信号集:未被处理的信号集合。

自定义信号集函数

所需头文件
        #include <signal.h>
函数
        int sigemptyset(sigset_t *set); //将 set 集合置空
        int sigfillset(sigset_t *set); // 将所有信号加入 set 集合
        int sigaddset(sigset_t *set, int signo); //将 signo 信号加入到 set 集合
        int sigdelset(sigset_t *set, int signo); //从 set 集合中移除 signo 信号
        int sigismember(const sigset_t *set, int signo); //判断信号是否存在于集合中
示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
//定义一个信号集
sigset_t set;
//清空set集
sigemptyset(&set);
//将SIGINT添加到set集合中
sigaddset(&set, SIGINT);
//将SIGTSTP添加到set集和中
sigaddset(&set, SIGTSTP);
if (sigismember(&set, SIGINT))
{
printf("SIGINT是在set集合中\n");
}
else
{
printf("SIGINT不在set集合中\n");
}
//将SIGINT从集合中删除
sigdelset(&set, SIGINT);
if (sigismember(&set, SIGINT))
{
printf("SIGINT是在set集合中\n");
}
else
{
printf("SIGINT不在set集合中\n");
}
return 0;
}

信号阻塞集

概述
信号阻塞集也称信号屏蔽集、信号掩码。
每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。
信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它 , 直到进程准备好时再将信号通知进程)。 所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除, 且对应的信号在被阻塞时发生了,进程将会收到相应的信号。
我们可以通过 sigprocmask() 修改当前的信号阻塞集来改变信号的阻塞情况。
sigprocmask 函数
作用
检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。
函数
所需头文件
#include <signal.h>
函数
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数
how: 信号阻塞集合的修改方法 , 3 种情况 :
SIG_BLOCK: 添加
向信号阻塞集合中添加 set 信号集 , 新的信号掩码是 set 和旧信号掩码的并集. 相当于 mask=mask|set
SIG_UNBLOCK: 删除
从信号阻塞集合中删除 set 信号集 , 从当前信号掩码中去除 set 中的信号 . 相当于mask=mask&~set
SIG_SETMASK: 替换
将信号阻塞集合设为 set 信号集 , 相当于原来信号阻塞集的内容清空 , 然后按照set 中的信号重新设置信号阻塞集。相当于 mask=set
set: 要操作的信号集地址。若 set NULL, 则不改变信号阻塞集合 , 函数只把当前信号阻塞集合保存到oldset
oldset: 保存原先信号阻塞集地址
示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
//定义一个信号集
//vscode中编写的sigset_t会报错,不管
sigset_t set;
//清空set集
sigemptyset(&set);
//将SIGINT添加到set集合中
sigaddset(&set, SIGINT);
//将set集合添加到阻塞集
//vscode中编写的SIG_BLOCK,SIG_BLOCK会报错不管
sigprocmask(SIG_BLOCK, &set, NULL);
printf("SIGINT信号将在5秒后从阻塞集中删除\n");
sleep(5);
sigprocmask(SIG_UNBLOCK, &set, NULL);
while (1);
return 0;
}

  • 41
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZHANGα

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值