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、创建并打开管道

int pipe(int pipefd[2]);

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

        参数:

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

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

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

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

2、读写

ssize_t read(int fd, void *buf, size_t count);

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

        参数:

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

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

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

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

ssize_t write(int fd, const void *buf, size_t count);

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

        参数:

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

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

                count:要写入的字节数。

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

3、关闭管道

int close(int fd);

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

二、有名管道

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

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

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

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

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

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

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

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

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

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

read(fd_read, buffer, sizeof(buffer));
write(fd_write, buffer, sizeof(buffer));

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

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() 函数可以向指定进程发送信号。

kill -9 1000  # 向进程 ID 为 1000 的进程发送 SIGKILL (9) 信号

        2. 使用 kill() 函数发送信号

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

 参数:

        pid:目标进程的进程 ID。

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

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

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

int raise(int sig);

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

unsigned int alarm(unsigned int seconds);

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

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

int pause(void);

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

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

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

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

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

        6、信号处理函数 signal()

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

  参数:signum:信号编号。

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

        SIG_DFL:默认处理。

        SIG_IGN:忽略处理。

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

#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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值