IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux系统编程——进程间通信(IPC) -> 正文阅读

[系统运维]Linux系统编程——进程间通信(IPC)


概述

  • 进程是一个独立的资源分配单元,不同进程之间相互独立。不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信(
    IPC:Inter Processes Communication )

  • 进程间通信的功能:

    • 数据传输:一个进程需要将它的数据发送给另一个进程。
    • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
    • 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
    • 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
  • Linux 操作系统支持的主要进程间通信的通信机制:

在这里插入图片描述


信号

  • 信号是 Linux 进程间通信的最古老的方式,是软中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式
  • 信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件

信号的编号

  • 每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
  • 信号定义在signal.h头文件中,信号名都定义为正整数,信号名定义路径:/usr/include/i386-linux-gnu/bits/signum.h
  • 具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用
  • 编号为 1 ~ 31 的信号为传统 UNIX 支持的信号,是不可靠信号(非实时的),编号为 32 ~ 63 的信号是后来扩充的,称做可靠信号(实时信号)
  • 不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。非可靠信号一般都有确定的用途及含义, 可靠信号则可以让用户自定义使用。
1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

不可靠信号简介:

编号信号作用
1SIGHUP本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一 session 内的各个作业, 这时它们与控制终端不再关联。此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
2SIGINT程序终止( interrupt )信号, 在用户键入 INTR 字符(通常是 Ctrl + C )时发出,用于通知前台进程组终止进程。
3SIGQUIT和 SIGINT 类似, 但由 QUIT 字符(通常是 Ctrl + / )来控制. 进程在因收到 SIGQUIT 退出时会产生 core 文件, 在这个意义上类似于一个程序错误信号。
4SIGILL执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。
5SIGTRAP由断点指令或其它 trap 指令产生. 由d ebugger 使用。
6SIGABRT调用 abort 函数生成的信号。
7SIGBUS非法地址, 包括内存地址对齐( alignment )出错。比如访问一个四个字长的整数, 但其地址不是 4 的倍数。它与 SIGSEGV 的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。
8SIGFPE在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。
9SIGKILL用来立即结束程序的运行。本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。
10SIGUSR1留给用户使用
11SIGSEGV试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。
12SIGUSR2留给用户使用
13SIGPIPE管道破裂。这个信号通常在进程间通信产生,比如采用 FIFO (管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到 SIGPIPE 信号。此外用Socket 通信的两个进程,写进程在写 Socket 的时候,读进程已经终止。
14SIGALRM时钟定时信号, 计算的是实际的时间或时钟时间。alarm 函数使用该信号。
15SIGTERM程序结束( terminate )信号, 与 SIGKILL 不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell 命令 kill 缺省产生这个信号。如果进程终止不了,我们才会尝试 SIGKILL。
16SIGSTKFLTLinux专用,数学协处理器的栈异常
17SIGCHLD子进程结束时, 父进程会收到这个信号。如果父进程没有处理这个信号,也没有等待( wait )子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略 SIGCHILD 信号,或者捕捉它,或者 wait 它派生的子进程,或者父进程先终止,这时子进程的终止自动由 init 进程来接管)。
18SIGCONT让一个停止( stopped )的进程继续执行。本信号不能被阻塞。可以用一个 handler 来让程序在由 stopped 状态变为继续执行时完成特定的工作。例如, 重新显示提示符。
19SIGSTOP停止( stopped )进程的执行。注意它和 terminate 以及 interrupt 的区别:该进程还未结束, 只是暂停执行。本信号不能被阻塞,处理或忽略。
20SIGTSTP停止进程的运行, 但该信号可以被处理和忽略。用户键入 SUSP 字符时(通常是 Ctrl + Z )发出这个信号。
21SIGTTIN当后台作业要从用户终端读数据时,该作业中的所有进程会收到 SIGTTIN 信号。缺省时这些进程会停止执行。
22SIGTTOU类似于 SIGTTIN,但在写终端(或修改终端模式)时收到。
23SIGURG有“紧急”数据或 out-of-band 数据到达 socket 时产生。
24SIGXCPU超过 CPU 时间资源限制。这个限制可以由 getrlimit/setrlimit 来读取/改变。
25SIGXFSZ当进程企图扩大文件以至于超过文件大小资源限制。
26SIGVTALRM虚拟时钟信号。类似于 SIGALRM,但是计算的是该进程占用的 CPU 时间。
27SIGPROF类似于 SIGALRM/SIGVTALRM,但包括该进程用的 CPU 时间以及系统调用的时间。
28SIGWINCH窗口大小改变时发出。
29SIGIO文件描述符准备就绪,可以开始进行输入/输出操作。
30SIGPWRPower failure
31SIGSYS非法的系统调用。

信号产生方式

  • 当用户按某些终端键时: 终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT,终端上按“Ctrl+\”键通常产生中断信号SIGQUIT,终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。

  • 硬件异常将产生信号:除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。

  • 软件异常将产生信号:当检测到某种软件条件已发生,并将其通知有关进程时,产生信号。

  • 调用函数将发送信号

  • 运行 kill 命令将发送信号: 此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程,例如使用kill -9 进程号可杀死程序。

信号的处理

  • 信号的处理有忽略,捕捉,和默认三种方式
  • 忽略信号: 大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景。
    • 例:使用函数signal(SIGINT,SIG_IGN);//将SIGINT信号忽略,函数执行后按下Ctrl + C中断信号时,会忽略它
  • 捕捉信号:告诉内核,用户希望如何处理某一种信号。当信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作:对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
  • 具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。

信号的发送和接收处理

  • 信号作为异步通讯的手段可以实现信号的发送和接收处理,以下面两种方式介绍

方式1:kill发送信号,signal注册一个函数处理信号

  • kill函数:

    #include <sys/types.h>
    #include <signal.h>
    
    int kill(pid_t pid, int sig);
    
  • 功能:给指定进程发送指定信号

  • 参数:

    • pid > 0: 将信号传送给进程 ID 为pid的进程。
    • pid = 0 : 将信号传送给当前进程所在进程组中的所有进程。
    • pid = -1 : 将信号传送给系统内所有的进程。
    • pid < -1 : 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
    • sig : 信号的编号,这里可以填数字编号,也可以填信号的宏定义
  • 返回值:成功返回0 ,失败返回-1

  • 示例代码kill.c:

    #include <signal.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <stdlib.h>
    int main(int argc,char **argv)
    {
        int signum = atoi(argv[1]);
        int pid = atoi(argv[2]);
    
        //kill(pid,signum);
        char cmd[32] = {0};
        sprintf(cmd,"kill -%d %d",signum,pid);
        system(cmd);
        
        return 0;
    }
    
  • signal函数:

    #include <signal.h>
    typedef void (*sighandler_t)(int);
    
    sighandler_t signal(int signum, sighandler_t handler);
    
  • 功能:注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞

  • 参数:

    • signum:信号的编号,数字编号或宏定义
    • handler: 取值有 3 种情况:
      • SIG_IGN:忽略该信号
      • SIG_DFL:执行系统默认动作
      • 信号处理函数名:自定义信号处理函数(处理函数的参数为signum)
  • pause函数等待信号到来:

    #include <unistd.h>
    int pause(void);
    /*******************
    功能:等待信号的到来(此函数会阻塞)。将调用进程挂起直至捕捉到信号为止,此函数通常用于判断信号是否已到。
    参数:无。
    返回值:直到捕获到信号才返回 -1,且 errno 被设置成 EINTR。
    ********************/
    
  • 示例代码signal.c:

    #include <signal.h>
    #include <stdio.h>
    #include <unistd.h>
    	 
    void handler(int signum) //信号处理函数
    {
    	printf("get signum:%d\n",signum);
    	switch(signum){
    		case 2:printf("SIGINT\n");break;
    		case 9:printf("SIGKILL\n");break;
    		case 10:printf("SIGUSR1\n");break;
    	}
    }
    
    int main()
    {
    	signal(SIGINT,handler);
     	signal(SIGKILL,handler);//KILL信号不能被处理,所以这个注册没意义,可以观察现象
    	signal(SIGUSR1,handler);
    	
    	pause();  //接收3个信号后正常退出
    	pause();
    	pause();
    	
    	return 0;
    }
    
    
  • 先执行signal.c,接收信号后的显示:

在这里插入图片描述

  • 打开另一个终端向这个进程发信号:

在这里插入图片描述

方式2:sigqueue发送信号和数据,sigaction接收

  • sigqueue函数:

    #include <signal.h>
    union sigval {
       int   sival_int;
       void *sival_ptr;
    };
    int sigqueue(pid_t pid, int sig, const union sigval value);
    
  • 功能:向指定进程发送一个信号和数据

  • 参数:

    • pid:目标进程的进程号
    • sig:信号编号
    • value:发送的消息,是一个存放信号附带数据的联合体,附带数据可以是一个整数也可以是一个指针
  • 返回值:若成功,返回 0;否则,返回 -1

  • 示例代码sigqueue.c:

    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc,char **argv)
    {
        int signum = atoi(argv[1]);
        int pid = atoi(argv[2]);
        union sigval value;
    
        value.sival_int = 666;
        sigqueue(pid,signum,value);
        printf("done\n");
    
        return 0;
    }
    
    
  • sigaction函数:

    #include<signal.h>
    
    int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
    
    struct sigaction {
    void (*sa_handler)(int); //信号处理程序,同signal,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
    void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
    sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
    int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
     };//回调函数句柄sa_handler、sa_sigaction只能任选其一
    
    
    
  • 功能:sigaction 可以用来查询或设置信号处理方式。

  • 参数:

    • signum:参数指出要捕获的信号类型,也就是信号的编号

    • act:参数指定新的信号处理方式,struct sigaction类型如果不为空说明需要对该信号有新的配置

      • sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数

      • sa_sigaction的参数有信号编号num,结构体siginfo,指针,指针为空表示没有数据,指针非空表示有数据,而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息

      • siginfo的成员有很多si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。关于发送过来的数据是存在两个地方的,sigval_t si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。

        siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                      hardware-generated signal
                                     (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
               sigval_t si_value;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               int      si_band;     /* Band event */
               int      si_fd;       /* File descriptor */
        }
        
        
      • sa_flags 用来设置信号处理的其他相关操作,下列的数值可用:
        SA_SIGINFO 提供附加信息,一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针
        SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
        SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
        SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号

    • oldact:备份,如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复

  • 示例代码sigaction.c:

    #include <signal.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    
    void handler(int signum, siginfo_t *info, void *text)
    {
            printf("signum:%d\n",signum);
            if(text != NULL){
                    printf("get data:%d\n",info->si_int);
                    printf("sig from:%d\n",info->si_pid);
            }
    }
    
    int main()
    {
            struct sigaction act;
            act.sa_sigaction = handler;
            act.sa_flags = SA_SIGINFO;
            printf("my pid:%d\n",getpid());
            sigaction(SIGUSR1,&act,NULL);
    
            pause();
            return 0;
    }
    
    
  • 编译执行sigaction.c(信号和数据来自sigqueue.c编译运行):

在这里插入图片描述


无名管道(pipe)

  • 管道也叫无名管道,它是是 UNIX 系统 IPC(进程间通信) 的最古老形式,所有的 UNIX 系统都支持这种通信机制。
  • 父进程调用pipe函数的时候,内核在执行pipe函数的时候就会在内核空间创建一个缓存,这个缓存就是无名管道

无名管道的特点:

1、半双工,数据在同一时刻只能在一个方向上流动。

2、数据只能从管道的一端写入,从另一端读出。

3、写入管道中的数据遵循先入先出的规则。

4、管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。

5、管道不是普通的文件,不属于某个文件系统,其只存在于内存中。

6、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。(一般默认64K

7、从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据。

8、管道没有名字,只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。(没关系的进程得不到管道的文件描述符,子进程的文件描述符是fork到的)

pipe函数

#include <unistd.h>

int pipe(int pipefd[2]);
/*******************************************
功能:创建无名管道。
参数:
	pipefd : 为 int 型数组的首地址,其存放了管道的文件描述符 pipefd[0]、pipefd[1]。
	当一个管道建立时,它会创建两个文件描述符 fd[0] 和 fd[1]。其中 fd[0] 固定用于读管道,而 fd[1] 固定用于写管道。一般文件 I / O 的函数都可以用来操作管道(lseek() 除外)。
返回值:	成功返回0,失败返回-1
************************************************/
  • 想要实现父子进程相互通信可以创建两个管道
  • 读管道:有数据时无论写端有无都会对管道内数据读取;无数据时若写端不存在则read返回0,若某进程有写端则read阻塞
  • 写管道:有读端就可以写;若无读端存在则管道破裂,该进程收到SIGPIPE信号(13)
  • fork后的半双工管道:
    在这里插入图片描述

父进程——|pipe|——>子进程示例

  • 父进程关闭读端,子进程关闭写端:

在这里插入图片描述

  • 示例代码pipe.c:

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    //int pipe(int pipefd[2]);
    int main()
    {
        int fd[2];
        int pid;
        char buf[32] = {0};
        if(pipe(fd) == -1){            //创建管道并判断是否创建失败
            printf("creat pipe fail\n");
        }
        pid = fork();                  //创建进程
        if(pid < 0){                   //如果创建进程失败
            printf("creat pid fail\n");
        }
        else if(pid > 0){             //进入父进程
            printf("this is father\n");
            close(fd[0]);             //关闭读
            write(fd[1],"wo nen die!",strlen("wo nen die!")); //往fd[1]写字符串
            sleep(1); //等子进程退出
        }
        else{                        //进入子进程
            printf("this is child\n"); 
            close(fd[1]);           //关闭写
            read(fd[0],buf,128);    //将父进程中写入的字符串从fd[0]中读出来
            printf("read father %s\n",buf); //打印
        }
        return 0;
    }
    
    

有名管道(FIFO)

  • 有名管道也叫命名管道,是在文件系统目录中存在的一个管道文件
  • 管道文件仅仅是文件系统中的标示,并不在磁盘上占据空间。在使用时,在内存上开辟空间,作为两个进程数据交互的通道。
  • FIFO不同于无名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,这样,即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据

mkfifo

  • 可以在终端使用shell指令mkfifo 文件名直接创建一个管道文件

  • 使用函数创建:

    #include <sys/types.h>
    #include <sys/stat.h>
    
    int mkfifo(const char *pathname, mode_t mode);
    
    • 功能:有名管道的创建。
    • 参数:
      • pathname : 普通的路径名,也就是创建后 FIFO 的名字。
      • mode : 文件的权限,与打开普通文件的 open() 函数中的 mode 参数相同。
    • 返回值: 成功返回0 ; 失败返回 -1。(文件存在也返回-1,此时errno = EEXIST)
  • open() 以只读方式打开 FIFO 时,要阻塞到某个进程为写而打开此 FIFO

  • open() 以只写方式打开 FIFO 时,要阻塞到某个进程为读而打开此 FIFO

示例代码

  • 发送mkfifo_w.c:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <fcntl.h>
    #include <unistd.h>
    	
    int main()
    {
    	
    	if(mkfifo("./named pipe",0600) == -1 && errno != EEXIST){
    		printf("creat named pipe failed\n"); 
    		perror("why");
    	}
    
    	int fd = open("./named pipe",O_WRONLY);
    	write(fd,"NB 666 My Baby!\n",strlen("NB 666 My Baby!\n"));
    		
    	close(fd);
    
    	return 0;
    }
    
  • 接收mkfifo_r.c:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <fcntl.h>
    #include <unistd.h>
    	
    int main()
    {
    	int i = 5;
    	char buf[128] = {0};
    
    	if(mkfifo("./named pipe",0600) == -1 && errno != EEXIST){
    		printf("creat named pipe failed\n"); 
    		perror("why");
    	}
    
    	int fd = open("./named pipe",O_RDONLY);
    	while(i--){
    		read(fd,buf,128);
    		printf("read from named pipe:\n%s",buf);
    		sleep(1);
    	}
    	close(fd);
    
    	return 0;
    }
    
  • 接收端显示:
    在这里插入图片描述


IPC对象

  • IPC 对象包含: 共享内存、消息队列和信号量

  • 每个IPC对象有唯一的ID(IPC对象创建的时候由系统分配的一个数字,只有创建IPC对象的进程可以获得ID,别的进程不知道这个ID号)

  • IPC对象创建后一直存在,直到被显式地删除

  • 每个IPC对象有一个关联的KEY(可以看成IPC对象的一个属性,通过KEY值,可以使不同的进程能够打开同一个IPC对象。创建IPC对象的进程把KEY值和IPC对象关联)

  • 查看IPC对象 ipcs

    • 查看共享内存对象ipcs -m
    • 查看消息队列对象ipcs -q
    • 查看信号量ipcs -s
  • iprm删除IPC对象

    ipcrm [ -M key | -m id | -Q key | -q id | -S key | -s id ] ...
    
  • 用ftok生成键值

    #include<sys/types.h>
    #include<sys/ipc.h>
    
    key_t ftok(const char *pathname,int proj_id);
    /*****************************
    函数功能:系统IPC键值的格式转换函数
    函数参数:const char *pathname: 文件路径名
    		int proj_id: 子序号,虽然是int类型,但是只使用8bits
    函数返回:成功:返回生成的key
    		出错:-1
    ******************************/
    
    • 如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值就为0x26010002

消息队列

  • 消息队列是消息的链接表,存放在内核中,一个消息队列由一个标识符(队列ID)来标识

  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级

  • 消息队列独立于发送与接收进程,进程终止时,消息队列中的内容不会被删除

  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按照消息的类型读取
    在这里插入图片描述

创建或打开一个消息队列

  • msgget函数:

    #include <sys/msg.h>
    int msgget(key_t key, int msgflg);	
    
  • 功能:创建一个新的或打开一个已经存在的消息队列。不同的进程调用此函数,只要用相同的 key 值就能得到同一个消息队列的标识符。

  • 参数:

    • key: IPC对象的key 值
    • msgflg: 标识函数的行为及消息队列的权限,其取值如下
      • IPC_CREAT:创建消息队列。 I
      • PC_EXCL: 检测消息队列是否存在。
      • 位或权限位:消息队列位或权限位后可以设置消息队列的访问权限,但可执行权限未使用。
  • 返回值: 成功返回消息队列的标识符 ;失败返回-1

消息队列的读写

  • 消息队列的数据格式由一个结构体定义,mtext的大小可由用户分配

    struct _msg
    {
    	long mtype;		 // 消息类型
    	char mtext[128]; // 消息正文
    };
    
    
  • 发送消息msgsnd

    #include <sys/msg.h>
    int msgsnd(  int msqid, const void *msgp, size_t msgsz, int msgflg);		
    
    • 功能: 将新消息添加到消息队列。

    • 参数:

      • msqid: 消息队列的标识符。
      • msgp: 待发送消息结构体的地址。
      • msgsz: 消息正文的字节数。
      • msgflg:函数的控制属性,其取值如下:
        • 0:msgsnd()调用阻塞直到条件满足为止。
        • IPC_NOWAIT: 若消息没有立即发送则调用该函数的进程会立即返回。
    • 返回值:成功0;失败-1

  • 接收消息msgrcv

    #include <sys/msg.h>
    ssize_t msgrcv( int msqid, void *msgp,  size_t msgsz, long msgtyp, int msgflg );
    
    • 功能:从标识符为 msqid 的消息队列中接收一个消息。一旦接收消息成功,则消息在消息队列中被删除。

    • 参数:

      • msqid:消息队列的标识符,代表要从哪个消息列中获取消息。

      • msgp: 存放消息结构体的地址。

      • msgsz:消息正文的字节数。

      • msgtyp:消息的类型。可以有以下几种类型:

        • msgtyp = 0:返回队列中的第一个消息。
        • msgtyp > 0:返回队列中消息类型为 msgtyp 的消息(常用)。
        • msgtyp < 0:返回队列中消息类型值小于或等于 msgtyp 绝对值的消息,如果这种消息有若干个,则取类型值最小的消息。在获取某类型消息的时候,若队列中有多条此类型的消息,则获取最先添加的消息,即先进先出原则。
      • msgflg:函数的控制属性。其取值如下:

        • 0:msgrcv() 调用阻塞直到接收消息成功为止。
        • MSG_NOERROR: 若返回的消息字节数比 nbytes 字节数多,则消息就会截短到 nbytes 字节,且不通知消息发送进程。
        • IPC_NOWAIT: 调用进程会立即返回。若没有收到消息则立即返回 -1。
    • 返回值: 成功读取消息的长度; 失败:-1

  • 控制消息队列对象msgctl

    #include <sys/ipc.h>
    #include <sys/msg.h>
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    
    • 功能:用来对消息队列的基本属性进行控制、修改。
    • 参数:
      • msqid:消息队列标识符。
      • cmd:执行的控制命令(在ipc.h中定义):
        • IPC_RMID :删除消息队列。从系统中删除给消息队列以及仍在该队列上的所有数据,这种删除立即生效。仍在使用这一消息队列的其他进程在它们下一次试图对此队列进行操作时,将出错,并返回EIDRM。 此命令只能由如下两种进程执行:
          • 1.其有效用户ID等于msg_perm.cuid或msg_perm.guid的进程。
          • 2.另一种是具有超级用户特权的进程。
        • IPC_SET :设置消息队列的属性。按照buf指向的结构中的值,来设置此队列的msqid_id结构。该命令的执行特权与上一个相同。
        • IPC_STAT:读取消息队列的属性。取得此队列的msqid_ds结构,并存放在buf*中。
        • IPC_INFO:读取消息队列基本情况。
      • buf:队列中的内容,一般为NULL

示例代码

  • A.c:

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    
    struct msgbuf {
            long mtype;       /* message type, must be > 0 */
    		char mtext[128];    /* message data */
    };
    
    
    int main()
    {
    	int msqid;
    	key_t key = ftok(".",2021);
            struct msgbuf wrBuf = { 888,"How are you? ^_^ "};
    	struct msgbuf reBuf;
    	msqid=msgget(key, IPC_CREAT|0777);
    	if(msqid == -1){
    		printf("msgget error");
    		exit(1);
    	}
    	
    	msgsnd(msqid,&wrBuf,strlen(wrBuf.mtext),0);
    	
    	msgrcv(msqid,&reBuf,sizeof(reBuf.mtext),999,0);
        printf("mtext from B:%s\n",reBuf.mtext);
    
    	msgctl(msqid,IPC_RMID,NULL);
    
    	return 0;
    }
    
    
    
  • B.c

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    	
    struct msgbuf {
        long mtype;       /* message type, must be > 0 */
    	char mtext[128];    /* message data */
    };
    
    
    int main()
    {
    	int msqid;
    	struct msgbuf reBuf;
    	struct msgbuf wrBuf = {999,"666666!"};
    	key_t key = ftok(".",2021);
    
    	msqid=msgget(key, IPC_CREAT|0777) ;
    	if(msqid == -1){
    		perror("msgget error");
    		exit(1);
    	}
    
    	if(msgrcv(msqid,&reBuf,sizeof(reBuf.mtext),888,0) == -1){
    		perror("msgrcv error");
    	}else{
    		printf("mtext from A:%s\n",reBuf.mtext);
    	}
    	
    	if(msgsnd(msqid,&wrBuf,strlen(wrBuf.mtext),0) == -1){
    		perror("msgsnd error");
    	}
    		
    	if(msgctl(msqid,IPC_RMID,NULL) == -1){
    		perror("msgctl error");
    	}
    
    	return 0;
    }
    	
    
  • 显示

    B进程:
    mtext from A:How are you? ^_^ 
    
    A进程:
    mtext from B:666666!
    

共享内存

  • 共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
  • 共享内存在进程空间的映射:
    在这里插入图片描述

使用共享内存通信的一般步骤

1、创建或者打开共享内存
2、进程A连接(映射)共享内存,写入数据
3、进程A断开
4、进程B连接(映射)共享内存,读取数据
5、进程B断开
6、释放共享内存

  • 示意图:
    在这里插入图片描述

创建共享内存

  • shmget函数:

    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmget(key_t key, size_t size,int shmflg);
    
    • 功能:创建或打开一块共享内存区。
    • 参数:
      • key:进程间通信键值,ftok() 的返回值。
      • size:该共享存储段的长度(字节)。
      • shmflg:标识函数的行为及共享内存的权限,其取值如下:
        • IPC_CREAT:如果不存在就创建
        • IPC_EXCL: 如果已经存在则返回失败
        • 位或权限位:共享内存位或权限位后可以设置共享内存的访问权限
    • 返回值: 成功返回共享内存标识符;失败返回-1。

共享内存的映射和解除映射

  • shmat函数:

    #include <sys/types.h>
    #include <sys/shm.h>
    
    void *shmat(int shmid, const void *shmaddr, int shmflg);
    
    • 功能: 将一个共享内存段映射到调用进程的数据段中

    • 参数:

      • shmid:共享内存标识符,shmget() 的返回值。
      • shmaddr:共享内存映射地址(若为 NULL 则由系统自动指定),推荐使用 NULL。
      • shmflg:共享内存段的访问权限和映射条件( 通常为 0 ),具体取值如下:
        • 0:共享内存具有可读可写权限。
        • SHM_RDONLY:只读。
        • SHM_RND:(shmaddr 非空时才有效)
    • 返回值: 成功返回共享内存段映射地址( 相当于这个指针就指向此共享内存 ) ;失败返回-1

  • shmdt函数:

    #include <sys/types.h>
    #include <sys/shm.h>
    
    int shmdt(const void *shmaddr);
    
    • 功能: 将共享内存和当前进程分离

    • 参数:

      • shmaddr:共享内存映射地址。
    • 返回值: 成功0 ;失败-1

共享内存操作函数

  • shmct函数:

    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    
    • 功能: 共享内存属性的控制。
    • 参数:
      • shmid:共享内存标识符。

      • cmd:函数功能的控制,其取值如下:

        • IPC_RMID:删除
        • IPC_SET:设置 shmid_ds 参数,相当于把共享内存原来的属性值替换为 buf 里的属性值。
        • IPC_STAT:保存 shmid_ds 参数,把共享内存原来的属性值备份到 buf 里。
        • SHM_LOCK:锁定共享内存段( 超级用户 )。
        • SHM_UNLOCK:解锁共享内存段。
        • SHM_LOCK 用于锁定内存,禁止内存交换。并不代表共享内存被锁定后禁止其它进程访问。
      • buf:shmid_ds 数据类型的地址,用来存放或修改共享内存的属性。

  • 返回值: 成功0 ;失败-1

示例代码

  • 写进程shm_w.c

    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    int main()
    {
        int shmid; //共享内存标识符
        char *shmaddr;
        key_t key;
        key = ftok(".",1);     //获取键值
        shmid = shmget(key,1024*4,IPC_CREAT|0666); //打开或者创建共享内存
        if(shmid == -1){
            printf("shmget NO OK\n");
            exit(-1);
        }
        shmaddr = shmat(shmid,0,0);  //共享内存连接到当前进程的地址空间
        printf("shmat ok\n");
        strcpy(shmaddr,"hello world"); //向内存中写入数据
        sleep(5);                 //睡眠5秒,等待内存数据被读走
        shmdt(shmaddr);            //断开进程和内存的连接
        shmctl(shmid,IPC_RMID,0);  //删除共享内存段
        printf("quit\n");
        return 0;
    }
    
    
  • 读进程shm_r.c

    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    int main()
    {
        int shmid;
        char *shmaddr;
        key_t key;
        key = ftok(".",1);        //获取键值
        shmid = shmget(key,1024*4,0); //打开创建的共享内存,获取内存ID,
        if(shmid == -1){
            printf("shmget no ok\n");
            exit(-1);
        }
        shmaddr = shmat(shmid,0,0); //共享内存连接到当前进程的地址空间
        printf("shmat ok\n");       //表示连接成功
        printf("data : %s",shmaddr); //将内存地址中的数据读出,打印
        shmdt(shmaddr);              //断开内存和当前进程的连接
        printf("quit\n");
        return 0;
    }
    

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-08-10 13:50:40  更:2021-08-10 13:51:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 19:45:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码