Linux高级编程-进程间通信(IPC)

进程之间共享数据的方式可以通过进程通信:

1、古老的通信方式:无名管道 有名管道 信号

2、IPC对象通信 :消息队列(用的相对少,这里不讨论)、共享内存(最高效)、 信号量集 3、socket通信:网络通信、线程信号

这里我们主要介绍下无名管道、有名管道、信号量集、socket通信在后序中会一次介绍。先说下管道相关的知识。

管道(pipe)是进程间通信(IPC)的一种机制,允许一个进程将数据传输到另一个进程。它具有以下特性和行为:

1. 半双工模式

半双工:管道是半双工的,只能单向传输数据,即数据只能从一个方向流动(从写端到读端)。如果需要双向通信,需要两个管道。

2. 特殊文件

特殊文件:管道是特殊文件,不支持定位操作。与普通文件不同,管道不支持 lseek 和 fseek,因为它们的读取是基于流的,不像普通文件那样可以随机访问。

3. 文件IO操作

文件IO:对管道的读写操作使用文件IO函数,例如 open、read、write 和 close。同样,使用标准库函数如 fgets 和 fread 也可以对管道进行操作,但需要配合文件描述符和流管理。

特性和行为:

**写阻塞:**如果管道的缓冲区(通常是 64KB)已满,进一步写入数据会阻塞,直到管道有足够的空间。这个行为保证了数据不会丢失。

**读阻塞:**如果管道为空,读操作会阻塞,直到管道中有数据可读。若管道关闭,读操作将返回 0,表示管道已经结束。

管道破裂:如果管道的读端被关闭,而写端仍存在,写操作会失败,通常返回 EPIPE 错误(Broken pipe)。也可以通过触发它关闭程序。

**正常结束:**如果管道的写端关闭且管道中没有数据,读操作将返回 0,表示管道已经关闭,没有更多数据可读。

gdb调试中可以使用 set folloe -fork(回车) -mode f child/parent选择进程进行调试

一、无名管道

无名管道(anonymous pipe)是一种用于在相关进程间进行简单数据通信的机制。它通过一对文件描述符提供了一个半双工的通信通道,即一个进程可以写入数据到管道的写端,另一个进程从管道的读端读取数据。以下是关于无名管道的特性:

**亲缘关系进程:**无名管道只能在具有亲缘关系的进程之间使用,通常是父子进程。创建无名管道的进程(通常是父进程)在 fork 调用之后,子进程会继承父进程的文件描述符,允许父子进程通过管道进行通信。

固定的读写端:无名管道有两个固定的文件描述符,一个用于读取(读端),关闭写的文件描述符pipefd[1] ;另一个用于写入(写端)、关闭读的文件描述符pipefd[0] 。读端和写端在 pipe 调用时被创建,且只能通过文件描述符 pipefd[0] 和 pipefd[1] 进行操作。也可以通过fdopen()转为文件流指针进行使用。

1、创建并打开管道

cpp 复制代码
int pipe(int pipefd[2]);

功能:创建一个无名管道,并初始化管道的读端和写端。

参数:

pipefd[0]:指向管道的读端的文件描述符。

pipefd[1]:指向管道的写端的文件描述符。

返回值:成功返回 0;失败返回 -1,并设置 errno 以指示错误。

注意事项:**fork 之前创建,**无名管道的创建应在 fork 调用之前进行,以便父进程和子进程共享同一管道的文件描述符。

2、读写

cpp 复制代码
ssize_t read(int fd, void *buf, size_t count);

**功能:**从管道的读端读取数据。

参数:

fd:管道的读端文件描述符(pipefd[0])。

buf:用于存储读取数据的缓冲区。

count:要读取的最大字节数。

**返回值:**成功时返回读取的字节数,失败时返回 -1。

cpp 复制代码
ssize_t write(int fd, const void *buf, size_t count);

**功能:**向管道的写端写入数据。

参数:

fd:管道的写端文件描述符(pipefd[1])。

buf:包含要写入数据的缓冲区。

count:要写入的字节数。

**返回值:**成功时返回写入的字节数,失败时返回 -1。

3、关闭管道

cpp 复制代码
int close(int fd);

功能:关闭管道的文件描述符。

二、有名管道

有名管道 (FIFO - First In, First Out) 是一种特殊类型的文件,它允许进程间通信。与匿名管道不同,有名管道在文件系统中可见,并且通过文件路径进行标识。它在多个不相关进程之间传递数据非常有用,任意进程都可以使用。

1、创建有名管道:使用 mkfifo 函数创建有名管道

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

参数:pathname:指定有名管道的路径和名称。

mode:指定文件权限,通常为八进制表示,如 0666(读写权限)。

返回值:成功返回 0,失败返回 -1。

2、打开有名管道:使用 open 函数打开有名管道,注意半双工的特性。

cpp 复制代码
int fd_read = open("./fifo", O_RDONLY);  // 读端打开
int fd_write = open("./fifo", O_WRONLY); // 写端打开

**注意:**有名管道尽量不使用 O_RDWR 方式打开,因为这会违背管道的半双工特性;不能使用 O_CREAT 选项创建文件,应使用 mkfifo 函数。

3、读写管道:通过标准文件 I/O 函数进行读写操作、和文件I\O一样的操作步骤。

cpp 复制代码
read(fd_read, buffer, sizeof(buffer));
write(fd_write, buffer, sizeof(buffer));

4、删除有名管道:使用 unlink 函数删除有名管道文件。

cpp 复制代码
int unlink(const char *pathname);
pathname:要删除的有名管道路径。
返回值:成功返回 0,失败返回 -1。

有名管道的关键点:

同步问题、当读端关闭时,写操作会返回错误,通常是 SIGPIPE 信号;当写端关闭时,读操作返回 0。读写端必须同时存在,否则 open 函数会阻塞,直到另一端打开。亲缘关系进程中的使用、有名管道可以在 fork 之后的亲缘关系进程中使用。

三、信号通信

信号的响应方式分为以下几种:

Term(终止):默认操作是终止进程。

Ign(忽略):默认操作是忽略信号。

Core (生成核心转储文件):默认操作是终止进程并生成一个核心转储文件,用于调试。

例如:gdb a.out -c core 用于分析核心转储文件。

Stop (停止):默认操作是停止进程。

Cont (继续):默认操作是继续执行已停止的进程。

**1、信号发送:**通过 kill 命令或系统调用 kill() 函数可以向指定进程发送信号。

cpp 复制代码
kill -9 1000  # 向进程 ID 为 1000 的进程发送 SIGKILL (9) 信号
  1. 使用 kill() 函数发送信号
cpp 复制代码
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

参数:

pid:目标进程的进程 ID。

sig:要发送的信号编号(可以通过 kill -l 查看所有信号的编号和名称)。

返回值:成功返回 0,失败返回 -1。

3、自发信号:raise() 函数

cpp 复制代码
int raise(int sig);

4、定时信号:alarm() 函数

cpp 复制代码
unsigned int alarm(unsigned int seconds);

作用:在指定的时间之后,系统会自动向进程发送 SIGALRM 信号。常用于实现定时功能。

5、暂停进程:pause() 函数

cpp 复制代码
int pause(void);

作用:使进程暂停执行,直到收到信号;信号的接收和处理。

每个进程对信号有三种默认响应方式:

默认处理:系统默认的处理方式,如终止进程。

忽略处理:忽略某些信号,如 SIGKILL (9) 和 SIGSTOP (19) 不可忽略。

自定义处理:通过信号捕获机制,自定义信号的处理逻辑。

6、信号处理函数 signal()

cpp 复制代码
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

参数:signum:信号编号。

handler:信号处理函数,可以是以下三种宏之一:

SIG_DFL:默认处理。

SIG_IGN:忽略处理。

自定义处理函数:定义特定的处理逻辑。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
void handle(int num)
{
    pid_t pid = wait(NULL);
    printf("wait pid :%d\n",pid);
}
int main(int argc, char *argv[])
{
    signal(SIGCHLD,handle);
    pid_t pid = fork();
    if(pid>0)
    {
        while(1)
        {
            printf("father processing...\n");
            sleep(1);
        
        }
    }
    else if(0 == pid) 
    {
        printf("child pid %d\n",getpid());
        sleep(rand()%3);
        printf("child will done %d\n",getpid());
        exit(0);
    }
    else 
    {
    
        perror("fork");
        return 1;
    }

    return 0;
}
相关推荐
qq_29750461几秒前
【解决】Linux更新系统内核后Nvidia-smi has failed...
linux·运维·服务器
_oP_i5 分钟前
.NET Core 项目配置到 Jenkins
运维·jenkins·.netcore
weixin_4373982113 分钟前
Linux扩展——shell编程
linux·运维·服务器·bash
小燚~15 分钟前
ubuntu开机进入initramfs状态
linux·运维·ubuntu
小林熬夜学编程22 分钟前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
炫彩@之星26 分钟前
Windows和Linux安全配置和加固
linux·windows·安全·系统安全配置和加固
上海运维Q先生27 分钟前
面试题整理15----K8s常见的网络插件有哪些
运维·网络·kubernetes
hhhhhhh_hhhhhh_36 分钟前
ubuntu18.04连接不上网络问题
linux·运维·ubuntu
冷心笑看丽美人44 分钟前
探秘 DNS 服务器:揭开域名解析的神秘面纱
linux·运维·服务器·dns
wenxiaocsdn1 小时前
某科技局国产服务器PVE虚拟化技术文档
运维·服务器