进程间通信 —— 早期通信

发展

1、早期通信方式
2、AT&T 的贝尔实验室,对 Unix 早期的进程间通信进行了改进和扩充,形成了“system V IPC”
(InterProcess Communication),其通信进程主要局限在单个计算机内。
3、BSD(加州大学伯克利分校的伯克利软件发布中心),跳过了只能在同一计算机通信的限制,
形成了基于套接字(socket)的进程间通信机制。

进程间通信方式

1、早期通信:

无名管道(pipe),有名管道(fifo),信号(sem)
将数据写入管道、从管道中读出数据。

2、system V IPC:

共享内存(share memory),信号灯集(semaphore),消息队列(message queue)
共享内存:被两个或多个进程共享,通信效率最高。
信号灯集:控制同步与互斥。
消息队列:根据类型不同,匹配不同的接收/发送方。

3、BSD:

套接字(socket)

进程间的早期通信

无名管道(pipe)

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

无名管道的特点

1、只能用于具有亲缘关系的进程之间通信
2、半双工通信,具有固定的读端和写端;
单工: 只能单方面传输信息,(广播、电视……)
半双工: 可以双向传输信息,但同一时刻只能一个方向传输信息,(对讲机……)
全双工: 可以双向、同时传输信息,(电话……)
3、可以看作特殊的文件(但不是文件),对于无名管道的读写可以使用 文件IO函数 read、write
4、基于文件描述符进行通信
当一个无名管道建立,它会创建两个文件描述符,一个是固定的读端,一个是固定的写端。

函数

int pipe(int fd[2]); 
功能:创建无名管道
参数:文件描述符 	fd[0]:读端  	fd[1]:写端
返回值:成功 0
       失败 -1

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

注意事项

1、当管道中无数据时,读会阻塞

在这里插入图片描述

2、当管道中写满(64k)数据时,写阻塞;一旦有4k空间,写继续,直到写满为止

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

3、将写端关闭,读操作立即返回(管道中无数据时)

在这里插入图片描述

4、将读端关闭,继续写数据,会导致管道破裂

进程会收到内核发送过来的SIGPIPE信号(Broken pipe)。
在这里插入图片描述
在这里插入图片描述

练习:利用无名管道实现进程通信

// 父子进程实现通信,父进程循环从终端输入数据,子进程循环打印数据,当输入 quit 结束。

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

int main(int argc, char const *argv[])
{
    char buf[64] = {};

    int fd[2];
    if (pipe(fd)){
        perror("Failed: ");
        return -1;
    }

    pid_t pid = fork();
    if (pid < 0){

        perror("Failed: ");
        return -1;

    } else if (pid > 0){
        
        while (1){
            scanf("%s", buf);
            write(fd[1], buf, 64);          // 这里也不能用 strlen(buf)
            if (!strcmp(buf, "quit"))
                break;
        }
        wait(NULL);							// 阻塞等待子进程结束,回收子进程

    } else {
        while (1){
            read(fd[0], buf, 64);           // 此 buf 非彼 buf, 不能用 strlen(buf)
            if (!strcmp(buf, "quit"))
                break;
            printf("%s\n", buf);
        }
    }

    close(fd[0]);
    close(fd[1]);
    return 0;
}

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

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

有名管道(fifo)

有名管道的特点

1、有名管道可以使互不相关的两个进程互相通信
2、有名管道可以 通过路径名来指出,并且在文件系统中可见,但内容存放在内存中
3、通过 文件IO 来操作有名管道半双工通信
4、有名管道遵循 先进先出规则;
5、不支持 lseek() 操作。

函数

int mkfifo(const char *filename,mode_t mode);
功能:创健有名管道
参数:filename:有名管道文件名
      mode:权限
返回值:成功:0
       失败:-1,并设置errno号

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

注意对错误的处理方式:
如果错误是file exist时,注意加判断,如:if(errno == EEXIST) 或 if(errno == 17)

注意事项:

1、只写方式,写阻塞(open处),直到另一个进程将读打开

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

2、只读方式,读阻塞(open处),直到另一个进程将写打开

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

3、可读可写,若管道中无数据,读阻塞

在这里插入图片描述

练习:通过两个进程实现 cp功能

// Write_into_fifo.c

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    if (mkfifo("./fifo", 0666)){
        if (errno == EEXIST)                	// 或 errno == 17
            printf("File exists!\n");
        else {
            perror("Failed ");
            return -1;
        }
    }

    int fd_f = open("./fifo", O_WRONLY);		// 不要写 O_RDONLY !!!
    int fd_txt = open(argv[1], O_RDONLY);
    if (fd_f < 0 || fd_txt < 0){
        perror("Failed ");
        return -1;
    }

    char buf[64] = {};
    while (1){
        int num = read(fd_txt, buf, 64);		// 从 A文件 读入 数组
        if (num <= 0)
            break;
        write(fd_f, buf, num);					// 从 数组 写入 有名管道
    }
    
    close(fd_f);
    close(fd_txt);

    return 0;
}
// Read_from_fifo.c

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    if (mkfifo("./fifo", 0666)){
        if (errno == EEXIST)                	// 或 errno == 17
            printf("File exists!\n");
        else {
            perror("Failed ");
            return -1;
        }
    }

    int fd_f = open("./fifo", O_RDONLY);		// 不要写 O_WRONLY !!!
    int fd_txt = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd_f < 0 || fd_txt < 0){
        perror("Failed ");
        return -1;
    }

    char buf[64] = {};
    while (1){
        int num = read(fd_f, buf, 64);			// 从 有名管道 读入数组
        if (num <= 0)
            break;
        write(fd_txt, buf, num);				// 从 数组 写入 B文件
    }

    close(fd_f);
    close(fd_txt);

    return 0;
}

在这里插入图片描述

input.c --> 读源文件,写到管道里
1)创建管道
2)打开源文件,管道文件
3)循环读源文件,写管道
4)关闭文件描述符
output.c --> 读管道,写到目标文件里面
1)创建管道
2)打开目标文件,管道文件
3)循环读管道文件,写目标文件
4)关闭文件描述符
在这里插入图片描述
在这里插入图片描述

练习:两进程间实现全双工

在这里插入图片描述

有名管道 & 无名管道

在这里插入图片描述

信号

概念

1)信号是在软件层次上对中断机制的一种模拟,是异步通信方式;
2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件;
3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

信号的响应方式

1、忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即 SIGKILL 及 SIGSTOP。
2、捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。pause() 结束的条件。
3、执行缺省操作:Linux 对每种信号都规定了默认操作 。

信号种类

在这里插入图片描述

函数接口

kill
int kill(pid_t pid, int sig);
功能:信号发送
参数:pid:指定进程
   sig:要发送的信号
返回值:成功 0     
       失败 -1

在这里插入图片描述

raise
int raise(int sig);
功能:进程向自己发送信号
参数:sig:信号
返回值:成功 0   
       失败 -1

在这里插入图片描述

pause
int pause(void);
功能:用于将调用进程挂起,直到捕捉到信号为止。

在这里插入图片描述

alarm
unsigned int alarm(unsigned int seconds); 
功能:在进程中设置一个定时器
参数:seconds:定时时间,单位为秒
返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
注意:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    int a = alarm(5);       		// 这里不是最后一个 alarm, 不等待
    printf("%d\n", a);      		// a == 0, 第一次调用 alarm 返回值为 0 

    printf("Hola~ \n");

    sleep(2);                       // 等待 2s

    printf("%d\n", alarm(20));      // 再次调用 alarm 将返回上次所剩时间 == 5-2 == 3
                                	// 最后一个 alarm, 等待
    pause();

    return 0;
}

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

signal
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数
参数:signum:要处理的信号
      handler:信号处理方式
        	SIG_IGN:忽略信号
            SIG_DFL:执行默认操作
            handler:捕捉信号  				void handler(int sig){} //函数名可以自定义
返回值:成功:设置之前的信号处理方式
       失败:-1
1、忽略信号

signal(信号, SIG_IGN);

有两个信号不能忽略:即 SIGKILL 及 SIGSTOP。
在这里插入图片描述

2、缺省操作

signal(信号, SIG_DFL);
在这里插入图片描述

3、捕捉操作

void handler(int sig){
/* blablabla… */
}

void main(){
signal(信号, handler);
}

在这里插入图片描述

练习:司机和售票员问题

1)售票员捕捉 SIGINT(代表开车)信号,向司机发送 SIGUSR1 信号,司机打印(Let’s go)
2)售票员捕捉 SIGQUIT(代表停车)信号,向司机发送 SIGUSR2 信号,司机打印(Stop the bus)
3)司机捕捉 SIGTSTP(代表到达终点站)信号,向售票员发送 SIGUSR1 信号,
售票员打印(Please get off the bus)
4)司机等待售票员下车,之后司机再下车。
分析:由 4)可知,利用父子进程完成此练习 —> 售票员子进程,司机父进程
司机:
忽略:SIGINT、SIGQUIT
捕捉:SIGUSR1 —> (Let’s go)
SIGUSR2 —> (Stop the bus)
SIGTSTP —> 向售货员发送 SIGUSR1 —> 等待回收子进程
售票员:
忽略:SIGTSTP
捕捉:SIGINT —> 向司机发送 SIGUSR1
SIGQUIT —> 向司机发送 SIGUSR2
SIGUSR1 —> (Please get off the bus)—> 结束子进程

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

pid_t pid, pid_f, pid_c;

void handler(int sig){
    switch (sig)
    {
    case SIGINT:
        kill(getppid(), SIGUSR1);
        break;
    case SIGQUIT:
        kill(getppid(), SIGUSR2);		// 子进程获取父进程的ID,向父进程传送信号
        break;
    case SIGUSR1:
        if (getpid() == pid_f){
            printf("\nLet's go!\n");
        } 
        if (getpid() == pid_c){
            printf("\nPlease get off the bus!\n");
            exit(0);
        }
        break;
    case SIGUSR2:
        printf("\nStop the bus!\n");
        break;
    case SIGTSTP:
        kill(pid, SIGUSR1);			// 父进程获取子进程的ID,向子进程传送信号
        break;
    default:
        break;
    }
}

int main(int argc, char const *argv[])
{
    pid = fork();					// 父进程内返回子进程的ID,子进程内返回0
    if (pid < 0){
        perror("Failed ");
        return -1;

    } else if (pid == 0){
        
        pid_c = getpid();
        signal(SIGTSTP, SIG_IGN);
        signal(SIGINT, handler);
        signal(SIGQUIT, handler);
        signal(SIGUSR1, handler);

        while(1)					// 保证可以循环输入信号,不能写在 return 0 前面,
            pause();				// 否则执行不到就退出程序

    } else {
        pid_f = getpid();
        signal(SIGINT, SIG_IGN);
        signal(SIGQUIT, SIG_IGN);
        signal(SIGUSR1, handler);       
        signal(SIGUSR2, handler);
        signal(SIGTSTP, handler);
	
        wait(NULL);					// 等待回收子进程
        exit(0);					// 这里要退出父进程,后面的语句不再执行
    }
    return 0;
}

也可以将捕捉函数拆分成两个:
在这里插入图片描述

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值