ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

进程间通信IPC

2021-08-11 11:03:23  阅读:309  来源: 互联网

标签:IPC int 间通信 信号量 信号 进程 include



概述

  • 进程是一个独立的资源分配单元,不同进程之间相互独立。不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信( 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

不可靠信号简介:

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

信号产生方式

  • 当用户按某些终端键时: 终端上按“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

示例代码

  • 本例程未使用信号量,写端停留5秒等待读端读取信息,配合信号量使用的代码见文末

  • 写进程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;
    }
    

信号量

多任务编程中互斥和同步的概念

  • 互斥:是指散步在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序片段后才可以运行。最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。
  • 同步:是指散步在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。

信号量(Semaphore)

  • 信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源(比如上文说的共享内存)的访问。

  • 信号量可以用来保证两个或多个关键代码段不被并发调用

  • 在实际应用中两个进程间通信可能会使用多个信号量,因此 System V 的信号量以集合的概念来管理。

  • 信号量集合数据结构:struct semid_ds,此数据结构中定义了整个信号量集的基本属性。

    /* Obsolete, used only for backwards compatibility and libc5 compiles */
    struct semid_ds
    {
    	struct ipc_perm	sem_perm;		/* permissions .. see ipc.h */
    	__kernel_time_t	sem_otime;		/* last semop time */
    	__kernel_time_t	sem_ctime;		/* last change time */
    	struct sem	*sem_base;		/* ptr to first semaphore in array */
    	struct sem_queue *sem_pending;		/* pending operations to be processed */
    	struct sem_queue **sem_pending_last;	/* last pending operation */
    	struct sem_undo	*undo;			/* undo requests on this array */
    	unsigned short	sem_nsems;		/* no. of semaphores in array */
    };
    
  • 信号量数据结构:struct sem,此数据结构中定义了信号量的基本属性。

    /* One semaphore structure for each semaphore in the system. */
    struct sem
    {
    	int	semval;		/* current value *信号量的值*/
    	int	sempid;		/* pid of last operation *最后一个操作信号量的进程号*/
    	struct list_head sem_pending; /* pending single-sop operations */
    };
    

信号量的操作

  • 编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。

  • PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。(P表示通过,V表示释放)

    • P 操作:将sem减 1,相减后,如果 sem<0 ,则进程/线程进入阻塞等待,否则继续,表明 P 操作可能会阻塞
    • V 操作:将 sem 加 1,相加后,如果 sem>0 ,唤醒一个等待中的进程/线程,表明 V 操作不会阻塞
    • 只有sem大于0时P才不会对进程阻塞
    • P 操作是用在进入临界区之前,V 操作是用在离开临界区之后,这两个操作一般是成对出现的。
      在这里插入图片描述
  • 信号量用于互斥:
    在这里插入图片描述

  • 信号量用于同步:
    在这里插入图片描述

相关函数

  • 创建信号量数组

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    
    int semget(key_t key, int nsems, int semflg);
    
    • 功能: 创建或打开一个信号量集合,该集合中可以包含多个信号量。

    • 参数:

      • key:进程间通信键值,通过调用 ftok() 函数得到的键值
      • nsems:创建的信号量的个数。如果只是访问而不创建则可以指定该参数为 0,一旦创建了该信号量, 就不能更改其信号量个数,只要不删除该信号量,重新调用该函数创建该键值的信号量,该函数只是返回以前创建的值,不会重新创建。
      • semflg:标识函数的行为及信号量的权限,其取值如下:
        • IPC_CREAT:创建信号量。
        • IPC_EXCL:检测信号量是否存在。
        • 位或权限位:信号量位或权限位后可以设置信号量的访问权限,格式和 open 函数的 mode_ t一样(open() 的使用请点此链接),但可执行权限未使用。
    • 返回值: 成功返回信号量集标识符;失败返回 -1

  • semop函数对信号量组进行操作

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    
    int semop(int semid, struct sembuf *sops, size_t nsops);
    struct sembuf{
        unsigned short  sem_num;	/*信号量的序号*/
        short       sem_op;		/*信号量的操作值*/
        short       sem_flg;	/*信号量的操作标识*/
    };
    
    • 功能: 操作信号量,主要进行信号量加减操作。

    • 参数:

      • semid:信号量集标识符。
      • sops:操作信号量的结构体(struct sembuf)数组的首地址( 结构体定义在 sys/sem.h ),此结构体中的数据表明了对信号量进行的操作。
        • 结构体成员使用说明如下:

        • sem_num:信号量集中信号量的序号

        • sem_op 取值如下:

          • sem_op > 0:信号量的值在原来的基础上加上此值。
          • sem_op < 0:如果信号量的值小于 semop 的绝对值,则挂起操作进程。
          • sem_op = 0:对信号量的值进行是否为 0 测试。若为 0 则函数立即返回,若不为 0 则阻塞调用进程
          • 如果信号量的值大于等于 semop 的绝对值, 则信号量的值在原来的基础上减去 semop 的绝对值。
        • sem_flag 取值如下:

          • IPC_NOWAIT:在对信号量的操作不能执行的情况下使函数立即返回。
          • SEM_UNDO:当进程退出后,该进程对信号量进行的操作将被撤销。
      • nsops:操作信号量的结构体数组中元素的个数。
    • 返回值: 成功0 ;失败-1

  • semctl函数控制信号量的相关信息

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    
    int semctl(int semid, int semnum, int cmd, ...);
    
    union semun{
    	int			val;	/*信号量的值*/
    	struct semid_ds *buf;	/*信号量集合信息*/
    	unsigned short  *array;/*信号量值的数组*/
    	struct seminfo  *__buf;/*信号量限制信息*/
    };
    
    • 功能: 对信号量集合以及集合中的信号量进行操作。

    • 参数:

      • semid:信号量集标识符
      • semnum:集合中信号量的序号,指定对哪个信号量操作, 只对几个特殊的 cmd 操作有意义
      • cmd:信号量控制类型。semctl() 函数可能有3个参数,也可能有4个参数,参数的个数由 cmd 决定。第4个参数为联合体。
      • cmd 的取值如下:
        • GETVAL:获取信号量的值。此时函数有3个参数。semctl() 函数的返回值即为信号量的值

        • SETVAL:设置信号量的值。此时函数有4个参数。第4个参数为联合体中的val,其值为信号量的值。

        • IPC_STAT:获取信号量集合的信息。此时函数有4个参数。第4个参数为联合体中的__buf

        • IPC_SET:设置信号量集合的信息。此时函数有4个参数。第4个参数为联合体中的__buf

        • IPC_RMID:删除信号量集。此时函数有3个参数,第2个参数semnum不起作用

        • GETALL:获取所有信号量的值。此时函数有4个参数,第2个参数semnum不起作用。第4个参数为联合体中的array,其值为用来存放所有信号量值的数组的首地址。

        • SETALL:设置所有信号量的值 ,参数说明同上

        • IPC_INFO:获取信号量集合的限制信息。此时函数有4个参数,第2个参数semnum不起作用。

        • GETPID:获取信号的进程号,即最后操作信号量的进程。此时函数有3个参数。 semctl() 函数的返回值即为信号的进程号。

        • GETNCNT:获取等待信号的值递增的进程数。此时函数有3个参数。semctl() 函数的返回值即为进程数。

        • GETZCNT:获取等待信号的值递减的进程数。此时函数有3个参数。semctl() 函数的返回值即为进程数。

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

函数使用示例

  • 下例将sem设为0,父进程使用P操作会等待子进程V操作释放

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    #include <stdio.h>
    #include <unistd.h>
    
    union semun {
        int              val;    /* Value for SETVAL */
        struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
        unsigned short  *array;  /* Array for GETALL, SETALL */
        struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                    (Linux-specific) */
    };
    
    
    void pGetKey(int semid)
    {
    		struct sembuf set;
    		set.sem_num = 0;
    		set.sem_op = -1;
    		set.sem_flg = SEM_UNDO;
    		semop(semid, &set,1);
    		printf("get key\n");
    }
    
    void vPutKey(int semid)
    {
            struct sembuf set;
    		set.sem_num = 0;
            set.sem_op = 1;
            set.sem_flg = SEM_UNDO;
            semop(semid, &set,1);
            printf("put key\n");
     }
    
    int main()
    {
    	key_t key = ftok(".",2);
    	int semid = semget(key,1,IPC_CREAT|0666);
    	union semun seminit;
    	seminit.val = 0;
        semctl(semid,0,SETVAL,seminit);
    
    	int pid = fork();
    	if(pid > 0){
    		pGetKey(semid);	
    		printf("this is father\n");
    		vPutKey(semid);
    		semctl(semid,0,IPC_RMID);
    	}else if(pid == 0){
    		printf("this is child\n");
    		sleep(5);
    		vPutKey(semid);
    	}else{
    		printf("fork error!\n");
    	}	
    
    
    	return 0;
    }
    
    

信号量配合共享内存使用

  • 示意图:

在这里插入图片描述

  • 参考信号量的同步模型,建立两个信号量,一个为0,另一个为1

  • 写进程shm_w.c:

    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/sem.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    union semun {
        int              val;    /* Value for SETVAL */
        struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
        unsigned short  *array;  /* Array for GETALL, SETALL */
        struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                    (Linux-specific) */
    };
    
    
    void P(int semid,unsigned short num)
    {
    		struct sembuf set;
    		set.sem_num = num; //信号量在数组里的序号
    		set.sem_op = -1; //信号量的操作值
    		set.sem_flg = SEM_UNDO; //信号量的操作标识
    		semop(semid, &set,1);
    		printf("get key\n");
    }
    
    void V(int semid,unsigned short num)
    {
            struct sembuf set;
    		set.sem_num = num;
            set.sem_op = 1;
            set.sem_flg = SEM_UNDO;
            semop(semid, &set,1);
            printf("put key\n");
     }
     
    int main()
    {
    	key_t key1,key2;
        key1 = ftok(".",1);     //获取键值
        key2 = ftok(".",2);
        
        int shmid = shmget(key1,1024*4,IPC_CREAT|0666); //打开或者创建共享内存
        int semid = semget(key2,2,IPC_CREAT|0666);//打开或者创建信号量组
       	
       	union semun seminit; //信号量初始化
    	seminit.val = 1; //第一个信号量设置为1
        semctl(semid,0,SETVAL,seminit);
        seminit.val = 0;//第二个信号量设置为0
        semctl(semid,1,SETVAL,seminit);
        
        P(semid,0); //给第一个信号量上锁
        char *shmaddr = shmat(shmid,0,0);  //共享内存连接到当前进程的地址空间
        printf("shmat ok\n");
        strcpy(shmaddr,"hello world"); //向内存中写入数据
        shmdt(shmaddr);            //断开进程和内存的连接
        V(semid,1); //释放第二个信号量
        
        P(semid,0); //等待第一个信号量的释放
        shmctl(shmid,IPC_RMID,0);  //删除共享内存段
        semctl(semid,0,IPC_RMID);   //删除信号量组
      	printf("quit\n");
        return 0;
    }
    
    
  • 读进程shm_r.c:

    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/sem.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    union semun {
        int              val;    /* Value for SETVAL */
        struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
        unsigned short  *array;  /* Array for GETALL, SETALL */
        struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                    (Linux-specific) */
    };
    
    
    void P(int semid,unsigned short num)
    {
    		struct sembuf set;
    		set.sem_num = num; //信号量在数组里的序号
    		set.sem_op = -1; //信号量的操作值
    		set.sem_flg = SEM_UNDO; //信号量的操作标识
    		semop(semid, &set,1);
    		printf("get key\n");
    }
    
    void V(int semid,unsigned short num)
    {
            struct sembuf set;
    		set.sem_num = num;
            set.sem_op = 1;
            set.sem_flg = SEM_UNDO;
            semop(semid, &set,1);
            printf("put key\n");
     }
     
    int main()
    {
        key_t key1,key2;
        key1 = ftok(".",1);     //获取键值
        key2 = ftok(".",2);
        
        int shmid = shmget(key1,1024*4,IPC_CREAT|0666); //打开或者创建共享内存
        int semid = semget(key2,1,IPC_CREAT|0666);//打开或者创建信号量组
       	    
        P(semid,1); //等待第二个信号量释放
        char *shmaddr = shmat(shmid,0,0); //共享内存连接到当前进程的地址空间
        printf("shmat ok\n");       //表示连接成功
        printf("data : %s\n",shmaddr); //将内存地址中的数据读出,打印
        shmdt(shmaddr);              //断开内存和当前进程的连接
        V(semid,0); //释放第一个信号量
        
        printf("quit\n");
        return 0;
    }
    
  • 因为信号量的初始化在写端,所以先执行写端

  • 终端显示:

    • 写端:
      在这里插入图片描述

    • 读端:
      在这里插入图片描述


标签:IPC,int,间通信,信号量,信号,进程,include
来源: https://www.cnblogs.com/rainto/p/15127227.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有