Linux笔记 8 -- 进程通讯

 Linux 进程通讯

一、概述

目的:为什么进程间需要通信?
1,数据传输 :   一个进程需要将它的数据发送给另一个进程。
2,资源共享 :   多个进程之间共享同样的资源。
3,通知事件 :   一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。
4,进程控制 :   有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。
二、发展
Linux 进程间通信(IPC)由以下几部分发展而来:
1,UNIX进程间通信
2,基于System V进程间通信
3,POSIX进程间通信
        POSIX(Portable Operating System Interface)表示可移植操作系统接口。电气和电子工程师协会(Institute of Electrical and Electronics Engineers, IEEE)最初开发 POSIX 标准,是为了提高UNIX 环境下应用程序的可移植性。然而,POSIX 并不局限于 UNIX ,许多其他的操作系统,例如DEC OpenVMS 和 Microsoft Windows, 都支持 POSIX 标准。
        System V 也被称为 AT&T System V, 是 Unix 操作系统众多版本中的一支。

  • 现在Linux使用的进程间的通信方式包括:
  • 1,管道(pipe)和有名管道(FIFO)
  • 2,信号(signal)
  • 3,消息队列
  • 4,共享内存
  • 5,信号量
  • 6,套接字(socket)
         管道通信
一、什么是管道
  管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。 一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
二、管道创建
    管道包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者可用于运行于同一系统中任意两个进程间的通信。
    无名管道由 pipe() 函数创建:    int pipe(int filedis[2]);
    当一个管道建立时,它会创建两个文件描述符:    filedis[0]用于读管道, filedis[1]用于写管道。

    写入数组只能在管道尾部,读取数据只能在管道头部。

Linux笔记 8 -- 进程通讯_第1张图片

Linux笔记 8 -- 进程通讯_第2张图片

三、管道关闭
   关闭管道只需将这两个文件描述符关闭即可,可以使用普通的 close 函数逐个关闭。
四、管道读写
    管道用于不同进程间通信。通常先创建一个管道,再通过 fork 函数创建一个子进程,该子进程会继承父进程所创建的管道。注意事项:必须在系统调用 fork() 前调用 pipe() , 否则子进程将不会继承文件描述符。
例:

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

int main()
{
    int pipe_fd[2];
    pid_t pid;
    char buf_r[100];
    char* p_wbuf;
    int r_num;
  
    memset(buf_r, 0, sizeof(buf_r));
    
    //创建管道
    if (pipe(pipe_fd) < 0)
    {
         printf("pipe create error\n");
         return -1;
    }
    //创建子进程
    if ((pid = fork()) == 0)
    {
         close(pipe_fd[1]);
         sleep(2);
         if((r_num = read(pipe_fd[0], buf_r, 100)) > 0)
         {
              printf("%d numbers read from the pipe is %s\n", r_num, buf_r); 
         }
         close(pipe_fd[0]);
         exit(0);  
    }
    else if (pid > 0)
    {
         close(pipe_fd[0]);
         if (write(pipe_fd[1], "Hello", 5) != -1)
                printf("parent write1 Hello!\n");
         if (write(pipe_fd[1], "Pipe", 5) != -1)
                printf("parent write2 Pipe!\n");
         close(pipe_fd[1]);
    }
}
五、命名管道(FIFO)
命名管道和无名管道基本相同,但也有点不同:无名管道只能由父子进程使用;但是通过命名管道,不相关的进程也能交换数据。
创建:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* pathname, mode_t mode)
参数: pathname : FIFO 文件名  ;    mode     : 属性 (见文件操作章节)
一旦创建了一个FIFO,就可以用 open 打开它, 一般的文件访问函数 (close、 read 、write 等)都可用于 FIFO 。
操作:
当打开 FIFO 时,非阻塞标志(O_NONBLOCK)将对以后的读写产生如下影响:
1、没有使用 O_NONBLOCK : 访问要求无法满足时进程将阻塞。如试图读取空的 FIFO ,  将导致进程阻塞。
2、使用 O_NONBLOCK : 访问要求无法满足时不阻塞,立即出错返回, errno 是 ENXIO 。
例:fifo_read.c
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO "/tmp/myfifo"

main(int argc,char** argv)
{
	char buf_r[100];
	int  fd;
	int  nread;
	
	/* 创建管道 */
	if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
		printf("cannot create fifoserver\n");
	
	printf("Preparing for reading bytes...\n");
	
	memset(buf_r,0,sizeof(buf_r));
	
	/* 打开管道 */
	fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
	if(fd==-1)
	{
		perror("open");
		exit(1);	
	}
	while(1)
	{
		memset(buf_r,0,sizeof(buf_r));
		
		if((nread=read(fd,buf_r,100))==-1)
		{
			if(errno==EAGAIN)
				printf("no data yet\n");
		}
		printf("read %s from FIFO\n",buf_r);
		sleep(1);
	}	
	pause(); /*暂停,等待信号*/
	unlink(FIFO); //删除文件
}
fifo_write.c :

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO_SERVER "/tmp/myfifo"

main(int argc,char** argv)
{
	int fd;
	char w_buf[100];
	int nwrite;
		
	/*打开管道*/
	fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
	
	if(argc==1)
	{
		printf("Please send something\n");
		exit(-1);
	}
	
	strcpy(w_buf,argv[1]);
	
	/* 向管道写入数据 */
	if((nwrite=write(fd,w_buf,100))==-1)
	{
		if(errno==EAGAIN)
			printf("The FIFO has not been read yet.Please try later\n");
	}
	else 
		printf("write %s to the FIFO\n",w_buf);
}

         信号通信
一、信号(signal)机制是 Unix 系统中最为古老的进程间通信机制,很多条件可以产生一个信号:
1、当用户按某些按键时,产生信号。
2、硬件异常产生信号:除数为0、无效的存储访问等等。这些情况通常由硬件检测到,将其通知内核, 然后内核产生适当的信号通知进程,例如,内核对正访问一个无效存储区的进程产生一个SIGSEGV信号。
3、进程用 kill 函数将信号发送给另一个进程。
4、用户可用 kill 命令将信号发送给其他进程。
二、信号类型
下面是几种常见的信号:
· SIGHUP   : 从终端上发出的结束信号
· SIGINT   : 来自键盘的中断信号(Ctrl+C)
· SIGKILL  : 该信号结束接收信号的进程
· SIGTERM  : kill 命令发出的信号
· SIGCHLD  : 标识子进程停止或者结束的信号
· SIGSTOP  : 来自键盘(Ctrl+Z)或调试程序的停止执行信号
三、信号处理
当某信号出现时,将按照下列三种方式中的一种进行处理:
1、忽略此信号   :  大多数信号都按照这种方式进行处理,但是有两种信号却决不能被忽略。他们是:SIGKILL 和 SIGSTOP 。 这两种信号不能被忽略的原因是:他们向超级用户提供了一种终止或停止进程的办法。
2、执行用户希望的动作  : 通知内核在某种信号发生时,调用一个用户函数。在用户函数中,执行用户希望的处理。
3、执行系统默认动作 : 对大多数信号的系统默认动作是终止该进程。
四、信号发送
发送信号的主要函数有 kill raise
区别:
kill 既可以向自身发送信号,也可以向其他进程发送信号。raise 函数是向进程自身发送信号。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signo)
int raise(int signo)

kill的pid参数有四种不同的情况:
1、pid > 0  : 将信号发送给进程 ID 为 pid 的进程。
2、pid == 0 :  将信号发送给同组的进程。
3、pid < 0  :  将信号发送给其进程组 ID 等于 pid 绝对值的进程。
4、pid == -1:  将信号发送给所有进程。
五、  Alarm
使用 alarm 函数可以设置一个时间值(闹钟时间),当所设置的时间到了时,产生 SIGALRM 信号。如果不捕捉此信号,则默认动作是终止该进程。
#include <unistd.h>
unsigned int alarm(unsigned int seconds)
参数: seconds: 经过了指定的 seconds 秒后会产生信号  SIGALRM 。
注意:每个进程只能有一个闹钟时间。如果在调用 alarm 时, 以前已为该进程设置过闹钟时间,而且他还没有超时,以前登记的闹钟时间则被新值代换。 如果有以前登记的尚未超过的闹钟时间,而这次 seconds 值是 0, 则表示取消以前的闹钟。
六、Pause
pause 函数使调用进程挂起直至捕捉到一个信号。
#include <unistd.h>
int pause(void)
只有执行了一个信号处理函数后,挂起才结束。
1、当系统捕捉到某个信号时,可以忽略该信号或是使用指定的处理函数来处理该信号, 或者使用系统默认的方式。
2、信号处理的主要方法有两种,一种是使用简单的 signal 函数,另一种是使用信号集函数组。
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int)

如何理解?
typedef void (*sighandler_t)(int) 
sighandler_t signal(int signum, sighandler_t handler))
func 可能的值是:
1、SIG_IGN : 忽略此信号
2、SIG_DFL : 按系统默认方式处理
3、信号处理函数名 : 使用该函数处理
例: mysignal.c

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void my_func(int sign_no)
{
	if(sign_no==SIGINT)
		printf("I have get SIGINT\n");
	else if(sign_no==SIGQUIT)
		printf("I have get SIGQUIT\n");
}
int main()
{
	printf("Waiting for signal SIGINT or SIGQUIT \n ");
	
	/*注册信号处理函数*/
	signal(SIGINT, my_func);
	signal(SIGQUIT, my_func);
	
	pause();
	exit(0);
}

   共享内存
一、共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
二、共享内存实现分为两个步骤:
1、创建共享内存,使用 shmget 函数。
2、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用 shmat 函数。
2.1 int shmget(key_t key, int size, int shmflg)
key 标识共享内存的键值:0/IPC_PRIVATE 。 当 key 的取值为 IPC_PRIVATE, 则函数shmget()将创建一块新的共享内存;如果key的取值为 0,而参数 shmflg 中又设置 IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。
返回值:成功返回共享内存标识符;失败返回 -1 。
2.2 int shmat(int shmid, char* shmaddr, int flag)
参数: shmid : shmget 函数返回的共享存储标识符  ;     flag  : 决定以什么方式来确定映射的地址 (通常为 0 )
返回值: 成功返回共享内存映射到进程中的地址;失败返回 -1 。
2.3 shmdt
int shmdt(char *shmaddr)
当一个进程不再需要共享内存时,需要把它从进程地址空间中脱离。
例:shmem.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PERM S_IRUSR|S_IWUSR
/* 共享内存 */

int main(int argc,char **argv) 
{ 
	int shmid; 
	char *p_addr,*c_addr; 
	
	if(argc!=2) 
	{ 
		fprintf(stderr,"Usage:%s\n\a",argv[0]); 
		exit(1); 
	}

	/* 创建共享内存 */	
	if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1) 
	{ 
		fprintf(stderr,"Create Share Memory Error:%s\n\a",strerror(errno)); 
		exit(1); 
	} 

	/* 创建子进程 */
	if(fork()) // 父进程写
	{ 
		p_addr=shmat(shmid,0,0); 
		memset(p_addr,'\0',1024); 
		strncpy(p_addr,argv[1],1024);
		wait(NULL); // 释放资源,不关心终止状态
		exit(0); 
	} 
	else       // 子进程读
	{ 
		sleep(1); // 暂停1秒		
		c_addr=shmat(shmid,0,0); 
		printf("Client get %p\n",c_addr); 
		exit(0); 
	} 
} 
百度百科中对这些函数的介绍:

shmat(把共享内存区对象映射到调用进程的地址空间)
所需头文件
#include <sys/types.h>
#include <sys/shm.h>
函数说明
连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问
函数原型
void *shmat(int shmid, const void *shmaddr, int shmflg)
函数传入值
shmid
共享内存标识符
shmaddr
指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
shmflg
SHM_RDONLY:为只读模式,其他为读写模式
函数返回值
成功:附加好的共享内存地址
出错:-1,错误原因存于error中
附加说明
fork后子进程继承已连接的共享内存地址。exec后该子进程与已连接的共享内存地址自动脱离(detach)。进程结束后,已连接的共享内存地址会自动脱离(detach)
错误代码
EACCES:无权限以指定方式连接共享内存
EINVAL:无效的参数shmid或shmaddr
ENOMEM:核心内存不足
shmget(得到一个共享内存标识符或创建一个共享内存对象)
所需头文件
#include <sys/ipc.h>
#include <sys/shm.h>
函数说明
得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符
函数原型
int shmget(key_t key, size_t size, int shmflg)
函数传入值
key
0(IPC_PRIVATE):会建立新共享内存对象
大于0的32位整数:视参数shmflg来确定操作。通常要求此值来源于ftok返回的IPC键值
size
大于0的整数:新建的共享内存大小,以字节为单位
0:只获取共享内存时指定为0
shmflg
0:取共享内存标识符,若不存在则函数会报错
IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错
函数返回值
成功:返回共享内存的标识符
出错:-1,错误原因存于error中
附加说明
上述shmflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限
错误代码
EINVAL:参数size小于SHMMIN或大于SHMMAX
EEXIST:预建立key所指的共享内存,但已经存在
EIDRM:参数key所指的共享内存已经删除
ENOSPC:超过了系统允许建立的共享内存的最大值(SHMALL)
ENOENT:参数key所指的共享内存不存在,而参数shmflg未设IPC_CREAT位
EACCES:没有权限
ENOMEM:核心内存不足

在Linux环境中,对开始申请的共享内存空间进行了初始化,初始值为0x00。
如果用shmget创建了一个新的消息队列对象时,则shmid_ds结构成员变量的值设置如下:
Ÿ shm_lpid、shm_nattach、shm_atime、shm_dtime设置为0。
Ÿ msg_ctime设置为当前时间。
Ÿ shm_segsz设成创建共享内存的大小。
Ÿ shmflg的读写权限放在shm_perm.mode中。
Ÿ shm_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。
shmdt函数原型
shmdt(断开共享内存连接)
所需头文件
#include <sys/types.h>
#include <sys/shm.h>
函数说明
与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
函数原型
int shmdt(const void *shmaddr)
函数传入值
shmaddr:连接的共享内存的起始地址
函数返回值
成功:0
出错:-1,错误原因存于error中
附加说明
本函数调用并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程
错误代码
EINVAL:无效的参数shmaddr
shmctl函数原型
shmctl(共享内存管理)
所需头文件
#include <sys/types.h>
#include <sys/shm.h>
函数说明
完成对共享内存的控制
函数原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
函数传入值
shmid
共享内存标识符
cmd
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
IPC_RMID:删除这片共享内存
buf
共享内存管理结构体。具体说明参见共享内存内核结构定义部分
函数返回值
成功:0
出错:-1,错误原因存于error中
错误代码
EACCESS:参数cmd为IPC_STAT,确无权限读取该共享内存
EFAULT:参数buf指向无效的内存地址
EIDRM:标识符为shmid的共享内存已被删除
EINVAL:无效的参数cmd或shmid
EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行
         消息队列
一、定义
unix早期通信机制之一的信号能够传送的信息量有限,管道则只能传送无格式的字节流,这无疑会给应用程序开发带来不便。消息队列(也叫做报文队列)则客服了这些缺点。
二、发展
消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式。进程可以向中按照一定的规则添加新消息;另一些进程则可以从消息队列中读走消息。
三、分类
目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息消息队列,系统V消息队列目前被大量使用。
四、持续性
系统V消息队列是随内核持续的,只有在内核重启或者人工删除时,该消息队列才会被删除。
五、键值
消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,必须提供该消息队列的键值。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(char* pathname, char proj)
        功能:返回文件名对应的键值
        参数   pathname : 文件名  ;   proj     : 项目名(不为0即可)
六、打开/创建
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg)
返回值  : 与键值 key 相对应的消息队列描述字。
参数: key    : 键值, 由 ftok 获得      ;    msgflg  : 标志位,如下:
IPC_CREAT   :  创建新的消息队列
IPC_EXCL    :  与 IPC_CREAT 一同使用,表示如果要创建的消息队列已经存在,则返回错误。
IPC_NOWAIT  :  读写消息队列要求无法得到满足时,不阻塞。
在以下两种情况下,将创建一个新的消息队列:
1,如果没有与键值key相对应的消息队列,并且msgflg中包含了 IPC_CREAT 标志位。
2,key参数为 IPC_RPIVATE。
int open_queue(key_t keyval)
{
    int qid;
    if ((qid = msgget(keyval, IPC_CREAT)) == -1)
    {
        return (-1);
    }
    return (qid);
} 
七、发送消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, struct msgbuf* msgp, int msgsz, int msgflg)
功能:向消息队列中发送一条消息。
参数:msqid : 已打开的消息队列id
      msgp  :存放消息的结构
      msgsz :消息数据长度
      msgflg:发送标志,有意义的msgflg标志为 IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。
struct msgbuf
{
    long mtype; //消息类型 > 0
    char mtext[1]; //消息数据的首地址
};
八、接收消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg)

功能:从 msqid 代表的消息队列中读取一个 msgtyp 类型的消息,并把消息存储在 msgp 指向的msgbuf结构中。在成功地读取了一条消息以后,队列中的这条消息将被删除。

int read_message(int qid, long type, struct mymsgbuf* qbuf)
{
    int result,length;
    length = sizeof(struct mymsgbuf) - sizeof(long);
    if ((result = msgrcv(qid, qbuf, length, type, 0)) == -1)
        return (-1);
    return (result);
}
例:msg.c

#include <sys/types.h>
#include <sys/msg.h>
#include <unistd.h>

struct msg_buf
{
    int mtype;
    char data[255];
};
 
int main()
{
        key_t key;
        int msgid;
        int ret;
        struct msg_buf msgbuf;
 
        key=ftok("/tmp/2",'a');
        printf("key =[%x]\n",key);
        msgid=msgget(key,IPC_CREAT|0666); /*通过文件对应*/

        if(msgid==-1)
        {
                printf("create error\n");
                return -1;
        }
 
        msgbuf.mtype = getpid();
        strcpy(msgbuf.data,"test haha");
        ret=msgsnd(msgid,&msgbuf,sizeof(msgbuf.data),IPC_NOWAIT);
        if(ret==-1)
        {
                printf("send message err\n");
                return -1;
        }
 
        memset(&msgbuf,0,sizeof(msgbuf));
        ret=msgrcv(msgid,&msgbuf,sizeof(msgbuf.data),getpid(),IPC_NOWAIT);
        if(ret==-1)
        {
                printf("recv message err\n");
                return -1;
        }
        printf("recv msg =[%s]\n",msgbuf.data);
 
}

          信号量
一、概念

信号量(又名:信号灯)与其他进程间通信方式不大相同,主要用途是保护临界资源。进程可以根据它判定是否能够访问某些共享资源。除了用于访问控制外,还可用于进程同步。
二、分类
1,二值信号灯:信号灯的值只能取0或1,类似于互斥锁。但两者有不同:信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
2,计数信号灯:信号灯的值可以取任意非负值。
三、创建/打开
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
key : 键值,由 ftok 获得
nsems :指定打开或者新创建的 信号灯集 中将包含信号灯的数目
semflg :标识,同消息队列
四、操作
int semop(int semid, struct sembuf* sops, unsigned nsops)
功能:对信号量进行控制。
semid : 信号量集的ID
sops : 是一个操作数组,表明要进行什么操作
nsops : sops所指向的数组的元素个数。

struct sembuf
{
    unsigned short sem_num; // semaphore index in array
    short sem_op; // semaphore operation
    short sem_flg; // operation flags
}
sem_num : 要操作的信号量在信号量集中的编号,第一个信号的编号是 0.
sem_op  :如果其值为正数,该值会加到现有的信号量中,通常用于释放信号量; 如果其值为负数,而其绝对值又大于信号的现值,操作将会阻塞, 直到信号值大于或者等于sem_op的绝对值,通常用于获取信号量; 如果其值为0,则操作将暂时阻塞,直到信号的值变为0。
sem_flg : 信号操作标志,可能的选择有两种:
         1,IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
         2,IPC_UNDO : 程序结束时(不论正常或不正常)释放信号量,这样做的目的在于 避免程序在异常情况下结束时未将锁定的资源解锁,照成该资源永远锁定。





你可能感兴趣的:(linux,消息队列,信号,共享内存,进程通讯)