马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
IPC 即 Inter-Process Communication,也就是进程间通信,它指的是在不同进程之间进行数据交换和和谐同步的机制。在操纵体系里,每个进程都有本身独立的内存空间,一般环境下不能直接访问其他进程的内存,以是须要借助 IPC 机制来实现进程间的信息交互与协作。
进程间通信常用的几种方式 1.管道通信:闻名管道,无名管道 2.消息队列3.共享内存4.信号量5.套接字管道
管道的本质是内核中的一块缓冲区,它在内核空间中开发了一段连续的内存区域,用于存储进程间传输的数据。在用户步伐看来,管道表现为一对文件形貌符,一个用于读操纵,另一个用于写操纵,进程可以像操纵普通文件一样对管道进行读写操纵,但现实上数据是在内存中的缓冲区里进行传输的。
匿名管道
数据传输:当一个进程向管道的写端写入数据时,数据会被复制到内核的管道缓冲区中;另一个进程从管道的读端读取数据时,数据会从管道缓冲区复制到该进程的用户空间。
亲缘关系要求:匿名管道只能用于具有亲缘关系的进程之间,如父子进程。这是由于创建管道后,只有创建管道的进程及其子进程才气访问这对文件形貌符,其他进程无法获取这些文件形貌符,也就无法访问该管道。
创建
匿名管道通过 pipe() 体系调用创建。pipe() 函数会在内核中创建一个管道,并返回两个文件形貌符,一个是读端文件形貌符(通常用 fd[0] 表现),另一个是写端文件形貌符(通常用 fd[1] 表现)- #include <unistd.h>
- int pipe(int fd[2])
- /*fd‐传出参数:
- fd[0]‐读端
- fd[1]‐写端
- 返回值:
- 0:成功
- ‐1:创建失败*/
复制代码 管道的读写
读操纵
有数据
read(fd[1]) 正常读,返回读出的字节数
无数据
写端被全部关闭,read返回0,相当于读文件到了尾部
没有全部关闭,read壅闭
写操纵
读端全部关闭
管道破裂,进程被停止
内核给当前进程发送信号SIGPIPE-13,默认处置处罚动作
读端没全部关闭
缓冲区写满了,write壅闭
缓冲区没满,write继续写,直到写满,壅闭
怎样设置非壅闭?
在管道的读写操纵中,默认环境下是壅闭模式,即当管道没有数据可读或者管道已满无法写入时,相应的读或写操纵会使进程壅闭等候。不过,你可以将管道设置为非壅闭模式,在这种模式下,当无法进行读写操纵时,操纵会立刻返回而不会壅闭进程。
fcntl-变参函数
复制文件形貌符-dup
修改文件属性-open的时候对应flag属性
设置方法- //获取原来的flags
- int flags = fcntl(fd[0],F_GETFL);
- //设置新的flags
- flags |=O_NONBLOCK;
- //将新的flags设置给文件描述符
- fcntl(fd[0],F_SETFL,flags);
复制代码
检察管道缓冲区的巨细
下令
ulimit -a
函数
fpathconf- long size = fpathconf(fd[1],_PC_PIPE_BUF);
- printf("size is %ld\n",size);
复制代码 举例:
父子进程利用管道通信
实现 ps aux| grep "bash"
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main()
- {
- int ret;
- int fd[2];
- ret = pipe(fd);
- if(ret == -1)
- {
- printf("create pipe failed!\n");
- exit(1);
- }
- pid_t pid;
- pid = fork();
- if(pid == -1)
- {
- printf("fork failed!\n");
- exit(1);
- }
- //ps aux
- if(pid>0)
- {
- close(fd[0]);
- dup2(fd[1],STDOUT_FILENO);//数据重定向
- execlp("ps","ps","aux",NULL);
- perror("execlp");
- exit(1);
- }
- //grep "hash"
- else if(pid == 0)
- {
- close(fd[1]);
- dup2(fd[0],STDIN_FILENO);
- execlp("grep","grep","bash","--color=auto",NULL);
- }
- // long size = fpathconf(fd[1],_PC_PIPE_BUF);
- // printf("size is %ld\n",size);
- printf("pipe[0] is %d\n",fd[0]);
- printf("pipe[1] is %d\n",fd[1]);
- close(fd[0]);
- close(fd[1]);
- return 0;
- }
复制代码 闻名管道
数据传输:与匿名管道类似,进程通过打开这个 FIFO 文件来获取管道的文件形貌符,然后进行读写操纵。数据同样是在内核的管道缓冲区中进行传输。
无亲缘关系限定:定名管道可以在恣意两个进程之间利用,由于它有一个文件体系中的路径名,不同的进程可以通过该路径名打开同一个 FIFO 文件来访问管道。
创建
闻名管道通过 mkfifo() 函数创建,它会在文件体系中创建一个特别范例的文件(FIFO 文件)。这个文件并不现实存储数据,只是作为一个访问管道的接口。- #include <sys/types.h>
- #include <sys/stat.h>
- int mkfifo(const char \*filename,mode_t mode);
- /*功能:创建管道文件
- 参数:管道文件文件名,权限,创建的文件权限仍然和umask有关系。
- 返回值:创建成功返回0,创建失败返回-1。*/
复制代码 fifo文件可以利用io函数进程操纵:open/close、read/write
不能实行lseek操纵
闻名管道的读写
读:- int main()
- {
- int ret,fd,nread;
- char readBuff[50] = {0};
- ret = mkfifo("/home/cc/ipc/myfifo",0755);
- if(ret == -1 )
- {
- return -1;
- }
- printf("create fifo success!\n");
- fd = open("./myfifo",O_RDONLY);
- if(fd < 0)
- {
- printf("open fifo failed!\n");
- return -1;
- }
- printf("open fifo success!\n");
- nread = read(fd,readBuff,50);
- printf("read %d byte from fifo %s:\n ",nread,readBuff);
- close(fd);
- return 0;
- }
复制代码 写:- int main()
- {
- int fd;
- char *str = "hello world!";
- fd = open("./myfifo",O_WRONLY);
- if(fd < 0)
- {
- printf("open fifo failed!\n");
- return -1;
- }
- printf("open fifo success!\n");
- write(fd,str,strlen(str));
- close(fd);
- return 0;
- }
复制代码
消息队列
特点:
消息队列是面向记录的,此中的消息具有特定的格式以及特定的优先级
消息队列独立于发送和吸收进程,进程停止时,消息队列及其内容仍存在
消息队列可以实现消息的随机查询,消息不肯定要先进先出的次序读取,也可以按消息的范例读取。
相干函数
1.创建或打开消息队列
在以下两种环境下,msgget将创建一个新的消息队列:
如果没有与键值key相对应的消息队列,而且flag中包罗了IPC_CREAT标志
key参数为IPC_PRIVATE- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- int msgget(key_t key, int msgflg);
- /*参数:
- key:和消息队列关联的key值
- msgflg:是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或
- 操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。
- 返回值:成功返回队列ID,失败则返回‐1*/
复制代码 2.发送消息
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
- /*参数:
- msgid:消息队列的ID
- msgp:指向消息缓冲区的指针,消息缓冲区的第一个成员必须是 long 类型,表示消息的类型。
- 常用结构体msgbuf如下:
- struct msgbuf
- {
- long mtype; //消息类型
- char mtext[N]; //消息正文
- }
- size:发送的消息正文的字节数
- flag:
- IPC_NOWAIT 消息没有发送完成函数也会立即返回
- 0:知道发送完成函数才返回
- 返回值:
- 成功:0
- 失败:‐1*/
复制代码 3.吸收消息
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
- /*参数:
- msgid:消息队列的ID
- msgp:要接收消息的缓冲区
- size:要接收的消息的字节数
- msgtype:
- 0:接收消息队列中第一个消息
- 大于0:接收消息队列中第一个类型为msgtyp的消息
- 小于0:接收消息队列中类型值不大于msgtyp的绝对值且类型值又最小的消息。
- flag:
- 0:若无消息函数一直阻塞
- IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。
- 返回值:
- 成功:接收到的消息i长度
- 出错:‐1*/
复制代码 4.控制消息队列
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- /*参数:
- msqid:消息队列的队列ID
- cmd:
- IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆
- 盖msgid_ds的值。
- IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
- IPC_RMID:删除消息队列
- buf:是指向 msgid_ds 结构的指针,它指向消息队列模式和访问权限的结构
- 返回值:
- 成功:0
- 失败:‐1*/
复制代码 5.ftok函数
体系创建IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常环境下,该id值通过ftok函数得到。- #include <sys/types.h>
- #include <sys/ipc.h>
- key_t ftok( char * fname, int id )
- /*参数:
- fname就时你指定的文件名(该文件必须是存在而且可以访问的)。
- id是子序号, 虽然为int,但是只有8个比特被使用(0‐255)。
- 返回值:
- 当成功执行的时候,一个key_t值将会被返回,否则 ‐1 被返回。*/
复制代码 举例:
通过父子进程让两个进程间相互收发消息
进程1:- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- struct msgbuf//magbuf结构体
- {
- long mtype;
- char mtest[128];
- char ID[4];
- };
- int main()
- {
- struct msgbuf sendbuf,readbuf;//定义读和写的结构体
- int msgid;
- key_t key;
- int readret;
- pid_t pid;
- key = ftok("a.c",1);//获取key值
- msgid = msgget(key,0755 | IPC_CREAT);//创建消息队列
- if(msgid == -1 )
- {
- printf("create message failed!\n");//创建失败就打印
- return -1;
- }
- printf("create message success! msgid is %d\n\n",msgid);//创建成功就打印
- system("ipcs -q");//查看队列信息
- //init msgbuf
- sendbuf.mtype = 100;//发送消息的类型是100
- pid = fork();//创建子进程
- if(pid>0)//父进程发送类型为100的消息
- {
- while(1)
- {
- memset(sendbuf.mtest,0,128);//初始化发送消息的数组
- printf("please input to message queue:\n");
- fgets(sendbuf.mtest,128,stdin);//向发送消息的数组输入消息
- //发送消息到消息队列
- msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
- }
- }
- if(pid == 0)//子进程读取类型为200的消息
- {
- while(1)
- {
- memset(readbuf.mtest,0,128);
- msgrcv(msgid,(void *)&readbuf,128,200,0);//读取消息到数组
- //打印读取到的消息
- printf("receive byte from message queue is: %s\n",readbuf.mtest);
- }
- }
- return 0;
- }
-
复制代码 进程2(读取范例为100的消息,发送范例为200的消息):- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- struct msgbuf
- {
- long mtype;
- char mtest[128];
- char ID[4];
- };
- int main()
- {
- struct msgbuf sendbuf,readbuf;
- int msgid;
- key_t key;
- int readret;
- pid_t pid;
- key = ftok("a.c",1);
- msgid = msgget(key,0755 | IPC_CREAT);
- if(msgid == -1 )
- {
- printf("create message failed!\n");
- return -1;
- }
- printf("create message success! msgid is %d\n\n",msgid);
- system("ipcs -q");
- //init msgbuf
- sendbuf.mtype = 200;
- pid = fork();
- if(pid == 0)//child process write 200
- {
- while(1)
- {
- memset(sendbuf.mtest,0,128);
- printf("please input to message queue:\n");
- fgets(sendbuf.mtest,128,stdin);
- //send message to message queue
- msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
- }
- }
- if(pid > 0)//parent process read 100
- {
- while(1)
- {
- memset(readbuf.mtest,0,128);
- msgrcv(msgid,(void *)&readbuf,128,100,0);
- printf("receive byte from message queue is: %s\n",readbuf.mtest);
- }
- }
- return 0;
- }
复制代码
共享内存
概念:
共享内存(Shared Memory)就是答应多个进程访问同一个内存空间,是在多个进程之间共享和转达数据最高效的方式。操纵体系将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存毗连到它们本身的地点空间中,如果某个进程修改了共享内存中的数据,别的的进程读到的数据也将会改变。
特点:
1.共享内存创建之后,不停存在于内核中,直到被删除或体系关闭
2.共享内存和管道不一样,读取后,内容仍然在共享内存中
相干函数
1.获取或创建共享内存
- #include <sys/ipc.h>
- #include <sys/shm.h>
- int shmget(key_t key, size_t size, int shmflg);
- /*参数:
- key:IPC_PRIVATE 或 ftok的返回值
- size:共享内存区大小
- shmflg:同open函数的权限位,也可以用8进制表示法
- 返回值:
- 成功:共享内存段标识符‐‐‐ID‐‐‐文件描述符
- 出错:‐1*/
复制代码
2.把共享内存毗连映射到当前进程的地点空间
- #include <sys/types.h>
- #include <sys/shm.h>
- void *shmat(int shm_id, const void *shm_addr, int shmflg);
- /*参数:
- shm_id:ID号
- shm_addr:映射到的地址,NULL为系统自动完成的映射
- shmflg:
- SHM_RDONLY共享内存只读
- 默认是0,表示共享内存可读写
- 返回值:
- 成功:映射后的地址
- 失败:NULL*/
复制代码
3.将进程里的地点映射删除
- #include <sys/types.h>
- #include <sys/shm.h>
- int shmdt(const void *shmaddr);
- /*参数:
- shmid:要操作的共享内存标识符
- 返回值:
- 成功:0
- 出错:‐1*/
复制代码
4.删除共享内存对象
- #include <sys/ipc.h>
- #include <sys/shm.h>
- int shmctl(int shm_id, int command, struct shmid_ds *buf);
- /*参数:
- shmid:要操作的共享内存标识符
- cmd :
- IPC_STAT (获取对象属性)‐‐‐ 实现了命令ipcs ‐m
- IPC_SET (设置对象属性)
- IPC_RMID (删除对象) ‐‐‐实现了命令ipcrm ‐m
- buf :指定IPC_STAT/IPC_SET时用以保存/设置属性
- 返回值:
- 成功:0
- 出错:‐1*/
复制代码 举例:
不同进程用共享内存进行通信
写消息进程:- #include <sys/ipc.h>
- #include <sys/shm.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main()
- {
- int shmid;
- key_t key;
- char *p;
- key = ftok("a.c",1);
- if(key < 0)
- {
- printf("ftok failed!\n");
- return -1;
- }
- printf("ftok success! key is : %x\n",key);
- shmid = shmget(key,128,0777 | IPC_CREAT);
- if(shmid < 0)
- {
- printf("create share memory failed!\n");
- return -1;
- }
- printf("create share memory success shmid = %d\n",shmid);
- system("ipcs -m");
- p = (char *) shmat(shmid,NULL,0);
- if(p == NULL)
- {
- printf("shmat function failed!\n");
- return -2;
- }
- //write to share memory
- fgets(p,128,stdin);
- sleep(10);
- shmdt(p);
- shmctl(shmid,IPC_RMID,0);
- return 0;
- }
复制代码 收消息进程:- #include <sys/ipc.h>
- #include <sys/shm.h>
- #include <stdio.h>
- #include <stdlib.h>
- int main()
- {
- int shmid;
- key_t key;
- char *p;
- key = ftok("a.c",1);
- if(key < 0)
- {
- printf("ftok failed!\n");
- return -1;
- }
- printf("ftok success! key is : %x\n",key);
- shmid = shmget(key,128,0);
- if(shmid < 0)
- {
- printf("create share memory failed!\n");
- return -1;
- }
- printf("create share memory success shmid = %d\n",shmid);
- system("ipcs -m");
- p = (char *) shmat(shmid,NULL,0);
- if(p == NULL)
- {
- printf("shmat function failed!\n");
- return -2;
- }
- printf("share memory data:%s",p);
- shmdt(p);
- return 0;
- }
复制代码
信号
信号通信,其实就是内核向用户空间进程发送信号,只有内核才气发信号,用户空间进程不能发送信号。
利用下令"kill -l"可以看到内核可以发送的信号
信号通信的框架
信号的发送(发送信号进程):kill、raise、alarm
信号的吸收(吸收信号进程) : pause()、 sleep、 while(1)
信号的处置处罚(吸收信号进程) :signal
1.信号的发送(发送信号进程)
kill- #include<signal.h>
- #include<sys/types.h>
- int kill(pid_t pid, int sig);
- /*参数:
- 函数传入值:pid
- 正数:要接收信号的进程的进程号
- 0:信号被发送到所有和pid进程在同一个进程组的进程
- ‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
- sig:信号
- 函数返回值:成功 0 出错 ‐1*/
复制代码 2.发信号给本身
raise: 发信号给本身 == kill(getpid(), sig)- #include<signal.h>
- #include<sys/types.h>
- int raise(int sig);
- /*参数:
- 函数传入值:sig:信号
- 函数返回值:
- 成功 0 出错 ‐1*/
复制代码 3.发送闹钟信号
alarm 与 raise 函数的比力:
类似点:让内核发送信号给当前进程
不同点:
alarm 只会发送SIGALARM信号
alarm 会让内审定时一段时间之后发送信号, raise会让内核立刻发信号- #include <unistd.h>
- unsigned int alarm(unsigned int seconds)
- /*参数:
- seconds:指定秒数
- 返回值:
- 成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
- 出错:‐1*/
复制代码 4.信号的吸收
pause 函数的作用是让调用进程暂停实行,直至收到一个能停止该进程或者使进程调用信号处置处罚函数的信号- #include <unistd.h>
- int pause(void);
- //函数返回值 成功:0,出错:‐1
复制代码 5.信号的处置处罚
收到信号的进程,处置处罚的方式:
1.进程的默认处置处罚方式(内核为用户进程设置的默认处置处罚方式)
A:忽略 B:停止进程 C: 暂停
2.本身的处置处罚方式:
本身处置处罚信号的方法告诉内核,这样你的进程收到了这个信号就会接纳你本身的的处置处罚方式- #include <signal.h>
- void (*signal(int signum, void (*handler)(int)))(int);
- /*参数
- signum:指定信号
- handler
- SIG_IGN:忽略该信号。
- SIG_DFL:采用系统默认方式处理信号
- 自定义的信号处理函数指针
- 函数返回值:
- 成功:设置之前的信号处理方式
- 出错:‐1
- signal函数有二个参数,第一个参数是一个整形变量(信号值),
- 第二个参数是一个函数指针,是我们自己写的处理函数;
- 这个函数的返回值是一个函数指针*/
复制代码
信号量
信号量聚集(可以包罗多个信号量)IPC对象是一个信号的聚集(多个信号量)
1.创建或者获取一个信号量集
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/sem.h>
- int semget(key_t key, int nsems, int semflg);
- /*函数参数:
- key:和信号量集关联的key值
- nsems: 信号量集中包含的信号量数目
- semflg:信号量集的访问权限
- 函数返回值:
- 成功:信号量集ID
- 出错:‐1*/
复制代码 2.控制信号量集
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/sem.h>
- int semctl(int semid, int semnum, int cmd, ...);
- /*函数参数:
- semid:信号量集ID
- semnum: 要修改的信号量集中信号量编号
- cmd :
- GETVAL:获取信号量的值
- SETVAL:设置信号量的值
- IPC_RMID:从系统中删除信号量集合
- 函数返回值:
- 成功:0
- 出错:‐1*/
- /*在使用可变参数时,通常需要一个 union semun 类型的参数,该联合体的定义如下:
- 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)
- };*/
复制代码 参数阐明:
semid
这是信号量集的标识符,由 semget 函数返回。它用于唯一标识要操纵的信号量集。
semnum
表现信号量会合要操纵的信号量的编号,编号从 0 开始。如果 cmd 下令不涉及特定的信号量(如 IPC_STAT、IPC_RMID 等),则该参数会被忽略。
cmd
这是一个整数,指定要实行的操纵下令,常见的下令如下:
SETVAL:将信号量会合指定编号的信号量的值设置为 arg.val。
GETVAL:获取信号量会合指定编号的信号量的当前值。
SETALL:将信号量会合全部信号量的值设置为 arg.array 数组中的值。
GETALL:将信号量会合全部信号量的当前值存储到 arg.array 数组中。
IPC_STAT:获取信号量集的状态信息,并将其存储在 arg.buf 指向的 struct semid_ds 布局体中。
IPC_SET:将 arg.buf 指向的 struct semid_ds 布局体中的信息设置为信号量集的新状态。
IPC_RMID:删除指定的信号量集,此时 semnum 和 arg 参数会被忽略。
...(可变参数)
这是一个可变参数,根据 cmd 下令的不同,须要转达不同范例的 union semun 参数。如果 cmd 下令不须要额外的参数(如 IPC_RMID),则可以不转达该参数。
返回值:
若操纵乐成,不同的 cmd 下令会有不同的返回值:
对于 GETVAL 下令,返回指定信号量的当前值。
对于 GETALL 下令,返回 0。
对于 IPC_STAT、IPC_SET、SETVAL、SETALL、IPC_RMID 等下令,返回 0 表现操纵乐成。
若操纵失败,返回 -1,并设置 errno 以指示错误范例。
PV操纵
P 操纵(申请资源)
功能:P 操纵用于申请资源,当一个进程实行 P 操纵时,它会实行获取一个资源。
实行过程:
起首将信号量的值减 1。
检查信号量的值:
如果信号量的值大于等于 0,阐明有可用资源,进程可以继续实行。
如果信号量的值小于 0,阐明没有可用资源,进程会被壅闭,进入等候队列,直到有其他进程开释资源。
V 操纵(开释资源)
功能:V 操纵用于开释资源,当一个进程实行 V 操纵时,它会开释一个资源。
实行过程:
起首将信号量的值加 1。
检查信号量的值:
如果信号量的值小于等于 0,阐明有进程在等候该资源,会叫醒等候队列中的一个进程。
如果信号量的值大于 0,阐明没有进程在等候该资源,进程继续实行。- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/sem.h>
- int semop(int semid, struct sembuf *sops, size_t nsops);
- /*参数:
- semid : 信号量的标识码。也就是semget()的返回值
- sops是一个指向结构体数组的指针。
- struct sembuf
- {
- unsigned short sem_num;//信号量编号;
- short sem_op; //对该信号量的操作。‐1 ,P操作,1 ,V操作
- short sem_flg; //0阻塞,1非阻塞
- };
- nsops: 操作信号量的个数
- 返回值:
- 如果操作成功,semop 函数返回 0。
- 如果操作失败,返回 -1 */
复制代码 |