进程间的通信——信号篇

进程间的通信

​ 想必大家都知道多进程的模型,可以这样想象,两个进程一直同时运行,而且步调一致,在fork之后,他们分别作不同的工作,也就是分岔了。这也是fork为什么叫fork的原因。事实也正是如此,当我们要用fork时,一般都是需要去做一个不同的工作。

​ 但是如果多个进程之间需要协同处理某个任务时(每个进程间的工作分工都不同,最终却需要所有进程间的处理结果整合),这时就需要进程间的同步和数据交流了。

​ 每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。

而常用的进程间通信(IPC,Inter-Process Communication)的方法有如下六种:

  • 信号(Signal )

  • 管道(Pipe)

  • socket

  • 信号量(Semaphore)

  • 共享内存(Shared Memory)

  • 消息队列(Message Queue)

这里先讲信号,关于其它的通信方式,后面再介绍

什么是信号?

​ 信号是一种软件中断,提供了一种处理异步事件的方法,也是进程间通信的唯一一个异步的通信方式。Unix中定义了很多信号,有很多条件可以产生信号,对于这些信号有不同的处理方式。

​ 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

信号是进程间通信机制中唯一的异步通信机制,可以在任何时候发送信号给某一进程,一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式。

  • 执行默认操作。Linux 对每种信号都规定了默认操作,例如,下面列表中的 SIGTERM 信号,就是终止进程的意思。Core 的意思是 Core Dump,也即终止进程后,通过 Core Dump 将当前进程的运行状态保存在文件里面,方便程序员事后进行分析问题在哪里。

  • 捕捉信号。我们可以为信号定义一个信号处理函数(安装信号)。当信号发生时,我们就执行相应的信号处理函数。

  • 忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。

​ 在Linux下我们可以通过 kill -l 来查看所有信号的定义。

[(img-MIVdzMOq-1658937671537)(C:\Users\86138\AppData\Roaming\Typora\typora-user-images\image-20220727204604262.png)]

如果我们要使用信号,man 7 signal 可以查看信号在什么条件下触发,以及它的默认处理动作是什么,了解了这些才能更好的运用

信号示例介绍

从键盘输入的信号

如果我们的代码内包含了一个死循环的逻辑并运行起来,那么我们通常是直接 ctrl+c 来终止它的,而ctrl+c本质上也是信号的一种。

注意:shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接收到由键盘键入的 ctrl+c 信号。

在运行进程的命令后加上&即可将进程放在后台运行,这时shell不必等待进程就可以接受新的命令,启动新的进程,但是后台进程无法使用ctrl+C结束,但是可以通过kill命令来结束进程。

kill -[信号值]【要终止进程的PID]

例如:kill -9 5678

接下来上实例:

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

int g_stop = 0;

void sig_handle(int signum)		//信号执行动作
{
        printf("Catch signal [%d]\n", signum);
        g_stop = 1;
}

int main(int argc, char *argv[])
{
        signal(SIGINT, sig_handle);		//安装信号2,ctrl+c  触发
        signal(SIGTERM, sig_handle);	//安装信号15,kill  -15 pid  触发

        while(!g_stop)
        {
        		sleep(2);
        		
                printf("running...\n");        
        }

        printf("Ending...\n");
}

结果分析:

ctrl+c

running...
running...
^CCatch signal [2]
running...
Ending...

kill -15 pid

...				//此处省略
running...
Catch signal [15]
running...
Ending...

了解过SIGINT和SIGTERM这两个信号之后,我们知道这两个信号的默认动作都是直接结束进程的,注意是直接结束

但是经过我们信号的安装和程序的设计,更改了它们默认的执行动作。并发现当程序收到这两个信号之后,并没有马上退出,而是去执行我们为它安排的”任务“,像上面这样的程序设计,我们发现可以让程序”优雅”的退出,即它自己一步一步执行到程序的结束,只不过是由“他人”来通知它结束的。并没有给它立马宣判死刑

注意:在这里,我们必须要知道有两个信号(SIGKILL、SIGSTOP)是不能被应用进程捕捉到的,它们两个也不能被忽略和阻塞。这是什么意思呢?也就是说,进程在任何时候收到这两个信号,就会立马终止(SIGKILL)或停止(SIGSTOP)进程的运行。

进程间信号是如何通信的?

在了解进程间信号的通信原理之前,我们必须先要了解一个函数,那就是kill()函数

kill()是一个计算机编程语言函数,Kill函数可以对进程发送signal();在linux里使用的Kill命令,实际上是对Kill()函数的一个包装。

函数原型:

int kill(pid_t pid, int sig);

函数功能:

给指定进程(pid)发送指定信号(sig)

tip:上面 kill -l 显示的信号中,有两个是用户自定义信号,一般使用这两个SIGUSR1(10)、SIGUSR2(12)信号

返回值

成功返回 0; 否则,返回 -1

进程间信号的简单通信

我们知道,父进程在创建子进程之后,究竟是父进程还是子进程先运行没有规定,这由操作系统的进程调度策略决定,而如果在某些情况下我们需要确保父子进程运行的先后顺序,则可以使用信号来实现进程间的同步。

下面是一个父子进程之间使用信号进行同步的例程。在下面的这个程序中,如果父进程先执行则进入到循环休眠等待状态,直到子进程给他发送信号之后才能跳出循环继续运行,这就可以确保子进程先执行它的任务。同样子进程在执行完成任务之后,就等待父进程给他发送信号之后才能退出,而父进程则通过调用wait()系统调用等待子进程退出后,父进程再退出。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

int g_child_stop = 0;
int g_parent_run = 0;

void sig_child(int signum)
{
        if(SIGUSR1 == signum)
        {
                g_child_stop = 1;
        }
}

void sig_parent(int signum)
{
        if(SIGUSR2 == signum)
        {
                g_parent_run = 1;
        }
}

int main(int argc, char *argv[])
{
        int             pid;
        int             w_status;

        signal(SIGUSR1, sig_child);		//安装信号
        signal(SIGUSR2, sig_parent);

        pid = fork();
        if(pid < 0)
        {
                printf("fork() failure: %s\n", strerror(errno));
                return -1;
        }

        else if(0 == pid)		//子进程
        {
                printf("child process start running and send a signal...\n");

                kill(getppid(), SIGUSR2);		//获取父进程的PID,并给其发信号SIGUSR2,通知其可以开始运行了

                while(!g_child_stop)		//等待父进程给子进程发送信号
                {
                        sleep(1);
                }
                printf("child process recieve signal and finish running...yeah\n");
        }

        else		//父进程
        {
                printf("parent process run, but not work...\n");
                
                while(!g_parent_run)		//等待子进程发送信号
                {
                        sleep(1);
                }
                printf("parent process start working ^_^\n");
                
                sleep(2);
                
                printf("parent process still working and send a signal...\n");
                
                kill(pid, SIGUSR1);			//给子进程发送信号SIGUSR1

                wait(&w_status);
                printf("parent wait child process die die die\n");
        }

        return 0;
}

运行结果如图所示:

parent process run, but not work...
child process start running and send a signal...
parent process start working ^_^
parent process still working and send a signal...
child process recieve signal and finish running...yeah
parent wait child process die die die
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
信号量是一种用于进程通信和同步的机制。它是一个计数器,用于保证在共享资源上的互斥访问。在Linux系统中,可以使用信号量来实现进程的同步和互斥。以下是信号量的基本概念: - 计数器:信号量的值是一个计数器,它可以被多个进程共享。 - P操作:当一个进程需要访问共享资源时,它必须执行P操作,该操作会将信号量的值减1。如果信号量的值为0,则进程将被阻塞,直到信号量的值大于0。 - V操作:当一个进程使用完共享资源后,它必须执行V操作,该操作会将信号量的值加1。如果有进程正在等待该信号量,则唤醒其中一个进程继续执行。 在ZUCC中,可以使用信号量来实现进程的同步和互斥。首先,需要使用semget函数创建一个信号量集合,并使用semctl函数对信号量进行初始化。然后,可以使用semop函数执行P和V操作。例如,下面是一个简单的示例程序,用于演示信号量的使用: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/sem.h> #define SEM_KEY 1234 union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main() { int semid, pid; union semun arg; struct sembuf sb; // 创建信号量集合 semid = semget(SEM_KEY, 1, IPC_CREAT | 0666); if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); } // 初始化信号量 arg.val = 1; if (semctl(semid, 0, SETVAL, arg) == -1) { perror("semctl"); exit(EXIT_FAILURE); } // 创建子进程 pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程执行P操作 sb.sem_num = 0; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop P"); exit(EXIT_FAILURE); } printf("Child process\n"); // 子进程执行V操作 sb.sem_num = 0; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop V"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } else { // 父进程执行P操作 sb.sem_num = 0; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop P"); exit(EXIT_FAILURE); } printf("Parent process\n"); // 父进程执行V操作 sb.sem_num = 0; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop V"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } return 0; } ``` 在上述代码中,创建了一个信号量集合,并将其初始化为1。然后,创建了一个子进程和一个父进程,它们分别执行P和V操作。由于信号量的初始值为1,因此父进程和子进程都可以顺利地执行。如果将信号量的初始值改为0,那么父进程和子进程都将被阻塞,直到有一个进程执行V操作为止。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值