文章目录
进程间通信
进程间通信介绍
- 进程间通信目的
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止
时要通知父进程)。 - 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另
一个进程的所有陷入和异常,并能够及时知道它的状态改变。
- 进程间通信发展
- 管道
- System V进程间通信
- POSIX进程间通信
- 进程间通信分类
- 管道
- 匿名管道pipe
- 命名管道
- System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
- POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
一. 管道
1. 管道的基本概念
- 管道是Unix中最古老的进程间通信的形式。
- 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间(匿名管道),完成数据传递。调用pipe系统函数即可创建一个匿名管道。
有如下特质:
- 其本质是一个伪文件(实为内核缓冲区)
- 由两个文件描述符引用,一个表示读端,一个表示写端。
- 规定数据从管道的写端流入管道,从读端流出。
ps: 管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道特点
-
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
-
管道提供流式服务
-
一般而言,进程退出,管道释放,所以管道的生命周期随着进程
-
一般而言,内核会对管道操作进行同步与互斥 (互斥:同一时间要么写要么读)
-
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
管道的局限性
- 数据自己读不能自己写。
- 数据一旦被读走,便不在管道中存在,不可反复读取。
- 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
- 只能在有公共祖先的进程间使用管道。
- 常见的通信方式有,单工通信、半双工通信、全双工通信。
管道读写规则
- 当没有数据可读时
- O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
- O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
- 当管道满的时候
- O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
- O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
- 管道对应的文件描述符被关闭
- 写端被关闭,则从管道中读取完所有数据后,继续read会返回0,不再阻塞
- 读端被关闭,write 操作会产生信号SIGPIPE,进而可能导致write进程退出
- 原子性
- 写入的数据量 不大于 PIPE_BUF时,linux将保证写入的原子性。
- 写入的数据量 大于 PIPE_BUF时,linux将不再保证写入的原子性。
- 其他注意事项:
- 管道的生命周期随进程,本质是内核中的缓冲区,命名管道文件只是标识,用于让多个进程找到同一块缓冲区,删除管道文件后,之前已经打开管道的进程依然可以通信
总而言之:
-
管道是最简单,效率最差的一种通信方式。
-
管道本质上就是内核中的一个缓存,当进程创建一个管道后,Linux会返回两个文件描述符,一个是写入端的描述符,一个是输出端的描述符,可以通过这两个描述符往管道写入或者读取数据。
-
如果想要实现两个进程通过管道来通信,则需要让创建管道的进程fork子进程,这样子进程们就拥有了- 父进程的文件描述符,这样子进程之间也就有了对同一管道的操作。
-
缺点:
- 半双工通信,一条管道只能一个进程写,一个进程读。
- 一个进程写完后,另一个进程才能读,反之同理。
2. 管道的创建
①. 匿名管道
pipe
头文件: unistd.h
int pipefd[2]={0};
int pipe(int pipefd[2]);
- 功能: 创建一无名管道
- 参数:pipefd:文件描述符数组
- pipefd[ 0 ]:读端文件描述符
- pipefd[ 1 ]:写端文件描述符
- 返回值:
- 成功返回 0 ;
- 失败返回 -1 且 设置错误码 error
pipe函数的应用
#include<iostream>
#include<cassert>
#include<unistd.h>
#include<cstdio> //等价于 stdio.h
#include<cstring> //等价于 string.h 以c++的风格将c语言的string头文件改写
int main()
{
int pipefd[2]={0};
int n = pipe(pipefd); //创建管道
assert(n == 0); //在debug下,assert会存在 realease 下,该行代码不起作用
(void)n; //防止编译器在realease下告警 n被定义未使用
cout<<"pipefd[0]:" <<pipefd[0]<<", pipefd[1]:"<<pipefd[1]<<endl;
return 0;
}
创建匿名管道图文详解:
-
父进程调用pipe函数
-
fork创建子进程
-
子进程关闭读端,父进程关闭写端
- 管道只能够进行单向通信,因此当父进程创建完子进程后,需要确认父子进程谁读谁写,然后关闭相应的读写端。
- 从管道写端写入的数据会存在内核缓冲,直到数据从管道中被读取
- 父进程读写端都打开本质是为了让子进程继承下来,最后确保管道只能进行单向通信
- 父子进程通信可不可以创建全局缓冲区来完成通信呢? 不可以 ! 进程运行具有独立性,写时拷贝
②. 命名管道
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件
注意:管道文件是以 p 开头的,如图:
创建命名管道
-
命令行上创建
#include <sys/stat.h> //头文件 mkfifo filename //创建一个FIFO命名管道文件
命名管道可以从命令行上创建
-
程序内创建
//命名管道也可以从程序里创建 int mkfifo(const char *filename,mode_t mode); // 文件名 文件权限 int main(int argc, char *argv[]) { mkfifo("p2", 0644); return 0; }
-
功能: 创建一命名管道
-
参数:
- filename:指定文件名
- mode: 指定文件权限
-
返回值:
- 成功返回0
- 失败返回-1。
- 如果返回EEXIST则表示管道的标识符文件已经存在了不需要重新创建。
命名管道的特性
- 如果管道文件被只 读 打开,会阻塞,直到管道文件也被 写 方式打开
- 如果管道文件被只 写 打开,会阻塞,直到管道文件也被 读 方式打开
匿名与命名管道的区别
- 匿名管道 由 pipe 函数创建并打开。
- 命名管道 由 mkfifo 函数创建,打开用 open
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完 成之后,它们具有相同的语义。
3. 删除管道
删除管道文件可以用如下命令
unlink fifo //删除管道文件
rm -f fifo
4. 管道的4种特殊情况
- 写端进程不写,读端进程一直读,那么此时会因为管道里面没有数据可读,对应的读端进程会被阻塞挂起,直到管道里面有数据后,读端进程才会被唤醒。
- 读端进程不读,写端进程一直写,那么当管道被写满后,对应的写端进程会被阻塞挂起,直到管道当中的数据被读端进程读取后,写端进程才会被唤醒。
- 写端进程将数据写完后将写端关闭,那么读端进程将管道当中的数据读完后,就会继续执行该进程之后的代码逻辑,而不会被挂起。
- 读端进程将读端关闭,而写端进程还在一直向管道写入数据,没有进程读取,那么写入的数据就没有意义,那么操作系统会将写端进程杀掉。
二、system V
-
管道通信本质是基于文件的,也就是说操作系统并没有为此做过多的设计工作,而system V IPC是操作系统特地设计的一种通信方式。但是不管怎么样,它们的本质都是一样的,都是在想尽办法让不同的进程看到同一份资源。
-
system V IPC提供的通信方式有以下三种:
- system V共享内存
- system V消息队列
- system V信号量
-
其中,system V共享内存和system V消息队列是以传送数据为目的的,而system V信号量是为了保证进程间的同步与互斥而设计的,虽然system V信号量和通信好像没有直接关系,但属于通信范畴。
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到系统内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据,减少了拷贝的次数,加快的程序的运行。
共享内存示意图
共享内存数据结构
truct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
1. 共享内存( shm )
shm基本概念
共享内存本质上就是内存中的一块区域,用于进程间通信使用。该内存空间由操作系统分配与管理。与文件系统类似的是,操作系统在管理共享内存时,不仅仅有内存数据块,同时还会创建相应结构体来记录该共享内存属性,以便于管理。
因此,共享内存不只有一份,可以根据需求申请多个。
进程之间进行通信的时候,会获取 到共享内存的地址,写端进程写入数据,读端进程通过直接访问内存完成数据读取。
-
共享内存优点
相比于管道而言,共享内存不仅能够用于非父子进程之间的通信,而且访问数据的速度也比管道要快。这得益于通信直接访问内存,而管道则需要先通过操作系统访问文件再获得内存数据。 -
共享内存缺点
用于进程间通信时,共享内存本身不支持阻塞等待操作。这是因为当读端读取数据后,数据并不会在内存中清空。因此读端和写端可以同时访问内存空间,即全双工。因为共享内存本质是进程直接访问内存,无法主动停止读取,如果读端不加以限制,那么将持续读取数据。同理,写端也会持续写入数据。换句话说,共享内存本身没有访问控制。 共享内存**没有提供同步的机制**,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。
其他注意事项
- 共享内存的删除操作并非直接删除,而是拒绝后续映射,只有在当前映射链接数为0时,表示没有进程访问了,才会真正被删除
- 共享内存生命周期随内核,只要不删除,就一直存在于内核中,除非重启系统(当然这里指的是非手动操作,可以手动删除)
shm函数
头文件:
#include <sys/ipc.h>
#include <sys/shm.h>
ftok
函数 ftok 把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键值(也称IPC key键值)。该 IPC 为后续创建共享内存做准备。
该函数需要包含以下两个头文件:
#include <sys/types.h>
#include <sys/ipc.h>
fork函数原型
//把从pathname导出的信息与id的低序8位组合成一个整数IPC键
key_t ftok(const char *pathname, int proj_id);
//pathname:指定的文件,此文件必须存在且可存取
//proj_id:计划代号(project ID)
返回值(key_t 一般为32位的 int 型的重定义):
- 成功:返回 key_t (即IPC 键值)
- 出错:返回 -1,错误原因存于error中
shmget
int shmget(key_t key, size_t size, int shmflg);
//示例
int main()
{
key_t n = ftok("/Linux/code",0x11223344);
int id = shmget(n , 4096 , IPC_CREAT | IPC_EXCL | 0664);
}
-
功能:用来创建共享内存
-
参数
-
key: 这个共享内存段名字(IPC 值,即key_t )
-
size: 共享内存大小,一般设置为 4096 的倍数
-
shmflg: 创建共享内存的方式以及设置权限,由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
//IPC_CREAT 可以单独使用 //如果共享内存不存在,则开辟内存,函数返回值是新开辟的共享内存的ID //如果已经存在,则沿用已有的共享内存,函数返回值是已有的共享内存的 //IPC_EXCL 无法单独使用 //需要配合 IPC_CREAT 使用,即 IPC_CREAT | IPC_EXCL //IPC_CREAT | IPC_EXCL //如果共享内存不存在,则重新开辟,函数返回值是新开辟的共享内存的ID //如果已经存在,则报错 //IPC_CREAT | IPC_EXCL | 0664 //开辟共享内存的同时,设置共享内存的访问权限
-
-
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
shmat
void *shmat(int shmid, const void *shmaddr, int shmflg);
// 一般为nullptr 一般为0
//示例
char* = (char*)shmat(shmid,nullptr,0);
-
功能:挂接共享内存至进程
-
参数:
-
shmid: 通过 shmget 创建的共享内存的返回值
-
shmaddr: 指定挂接地址, 通常传入 nullptr 即可, 让OS自动选择挂接地址
-
shmflg: 通常为0即可
-
-
返回值:
- 成功:返回一个void* 指针, 这里类似于malloc, 返回值需要根据使用来强制类型转换, 返回的地址是共享内存的起始地址,
- 失败:如果失败则返回 (void*) -1, 并且错误码 errno 被设置
shmdt
int shmdt(const void *shmaddr);
- 功能:将共享内存段与当前进程脱离
- 参数:
- shmaddr: 由 shmat 所返回的指针
- 返回值:
- 成功:返回 0
- 失败:返回 -1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
// IPC_RMID nullptr
-
功能:⽤于控制共享内存
-
参数:
-
shmid:由shmget返回的共享内存标识码
-
cmd:将要采取的动作(有三个可取值)
// IPC_ STAT:把shmid_ ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值 //IPC_ SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值 //IPC_RMID:删除共享内存段
-
buf: 指向⼀个保存着共享内存的模式状态和访问权限的数据结构。
-
-
返回值:
- 成功返回 0
- 失败返回 -1
2. 消息队列( msg )
msg基本概念
消息队列是消息的链表,存放在内核中并由消息队列标识符表示。
消息队列提供了一个从一个进程向另一个进程发送数据块的方法,每个数据块都可以被认为是有一个类型,接受者接受的数据块可以有不同的类型。
但是同管道类似,它有一个不足就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数(MSGMNB),系统上消息队列的总数上限(MSGMNI)。可以用cat /proc/sys/kernel/msgmax查看具体的数据。
内核为每个IPC对象维护了一个数据结构struct ipc_perm,用于标识消息队列,让进程知道当前操作的是哪个消息队列。每一个msqid_ds表示一个消息队列,并通过msqid_ds.msg_first、msg_last维护一个先进先出的msg链表队列,当发送一个消息到该消息队列时,把发送的消息构造成一个msg的结构对象,并添加到msqid_ds.msg_first、msg_last维护的链表队列。在内核中的表示如下:
msg函数
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
msgget
int msgget(key_t key, int msgflag);
//key =ftok() IPC_CREAT|IPC_EXCL|0666
//例: int msqid = msgget(key, IPC_CREAT | 0666);
-
功能:创建和访问一个消息队列
-
参数:
-
key:某个消息队列的名字,用ftok()产生
-
msgflag:有两个选项 IPC_CREAT和IPC_EXCL,单独使用IPC_CREAT,如果消息队列不存在则创建之,如果存在则打开返回;单独使用IPC_EXCL是没有意义的;两个同时使用,如果消息队列不存在则创建之,如果存在则出错返回。
-
-
返回值:成功返回一个非负整数,即消息队列的标识码,失败返回-1
msgsnd
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
struct msgbuf {
long mtype; /* 标志,代表接收特定标记的数据块 */
char mtext[1]; /* message data */
};
//例:
int send_msg(int msqid, int m_type, char *in)
{ //#define cilent_type 1
struct my_msgbuf buf;
buf.mtype = m_type;
strncpy(buf.mtext, in, sizeof in);
int n = msgsnd(msqid, (void *)&buf, sizeof buf.mtext, 0);
if (n < 0)
{
cerr << "msg_send error! eoorr:" << errno << " strerror:" << strerror(errno) << endl;
exit(1);
}
return 1;
}
-
功能:把一条消息添加到消息队列中
-
参数:
- msgid:由msgget函数返回的消息队列标识码
- msgp:指针指向准备发送的消息
- msgze:msgp指向的消息的长度(不包括消息类型的long int长整型)
- msgflg:默认为0
-
返回值:成功返回0,失败返回-1
msgrcv
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
- 功能:是从一个消息队列接受消息
- 参数:
- msgid:由msgget函数返回的消息队列标识码
- msgp:指针指向准备发送的消息
- msgz:msgp指向的消息的长度(不包括消息类型的long int长整型)
- msgtyp:接收指定标记的数据块
- msgflg:默认为0
- 返回值:成功返回实际放到接收缓冲区里去的字符个数,失败返回-1
msgctl
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
例: int n = msgctl(msgid, IPC_RMID, nullptr);
-
功能:消息队列的控制函数
-
参数:
-
msqid:由msgget函数返回的消息队列标识码
-
cmd:有三个可选的值,在此我们使用IPC_RMID
-
buf:一般为 nullptr
//IPC_STAT 把msqid_ds结构中的数据设置为消息队列的当前关联值 //IPC_SET 在进程有足够权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值 //IPC_RMID 删除消息队列
-
-
返回值:成功返回0,失败返回-1
3. 信号量
sem函数
semget
int semget(key_t key, int num_sems, int sem_flags);
-
功能:它的作用是创建一个新信号量或取得一个已有信号量。
-
参数:
-
key:整数值,可以理解为文件目录索引值、不同于id为该文件id名。
-
num_sems: 指定需要的信号量数目,它的值几乎总是1。
-
sem_flags:一组标志,当想要当信号量不存在时创建一个新的信号量 IPC_CREAT|0666
-
-
返回值:成功返回一个相应信号量集ID,失败返回-1.
semop
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
-
功能:它的作用是改变信号量的值,进行PV操作的函数。原型为:
-
参数:
-
sem_id: 是由semget返回的信号量标识符,
-
sem_opa:信号量结构体struct sembuf指针,该指针改变后的信号量
-
num_sem_ops: struct sembuf变量成员数量
-
//sembuf结构的定义如下:
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv)
P操作:sem_op= -1 <0,使得共享临界资源上锁,其它进程不得访问
V操作:sem_op= 1 >0,使得共享临界资源解锁,其它进程可以访问
semctl在初始化信号量的时候,union semum结构体中val就是信号量的初始化值,一般==1;
进行PV操作时,semop函数把 struct sembuf结构体中设置好的参数储存,此时val+1或-1,从而达到加锁解锁的目的,控制进程访问。
举个例子,就是两个进程共享信号量sv=val=1,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,sem_op使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。
semctl函数
int semctl(int sem_id, int sem_num, int cmd, ...);
- 功能:该函数用来直接控制信号量信息,初始化和删除信号量
- 参数:
- sem_id:信号量标示符
- sem_num:信号量集中有多个信号量,表示第几个信号量
- cmd:SETVAL 用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID 用于删除一个已经无需继续使用的信号量标识符。
三、指令操作
-
查看相关通信方式
ipcs -m //查看共享内存 ipcs -q //查看消息队列 ipcs -s //查看信号量
① 查看共享内存:
② 查看消息队列:
③ 查看信号量
nattch: 当前有多少进程挂接到该共享内存
-
删除相关通信方式
ipcrm -m [shmid] ipcrm -q [msgid] ipcrm -sv [semid] ipcrm -a //使用ipcrm -a选项可以删除所有进程间通信资源
四、内核IPC结构
结构代码
struct ipc_ids{
int size; /*entries数组的大小*/
int in_use; /*entries数组已使用的元素个数*/
int max_id;
unsigned short seq;
unsigned short seq_max;
struct semaphore sem; /*控制对ipc_ids结构的访问*/
spinlock_t ary; /*自旋锁控制对数组entries的访问*/
struct ipc_id* entries;
//柔性数组,可以一直扩大大小
//ipc_id_ary [size,*p[0/1/2/3];
// *p[0] *p[1] 指向 shmid_kerne sem_array msg_queue 这些数据结构
};
struct ipc_id{ struct ipc_perm *p;};
ipc_perm
struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
shmid_kerne
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
msg_queue
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* 队列上第一条消息,即链表头*/
struct msg *msg_last; /* 队列中的最后一条消息,即链表尾 */
time_t msg_stime; /* 发送给队列的最后一条消息的时间 */
time_t msg_rtime; /* 从消息队列接收到的最后一条消息的时间 */
time_t msg_ctime; /* 最后修改队列的时间*/
ushort msg_cbytes; /*队列上所有消息总的字节数 */
ushort msg_qnum; /*在当前队列上消息的个数 */
ushort msg_qbytes; /* 队列最大的字节数 */
ushort msg_lspid; /* 发送最后一条消息的进程的pid */
ushort msg_lrpid; /* 接收最后一条消息的进程的pid */
};
sem_array
struct semid_ds {
struct ipc_perm sem_perm; /*IPC权限 */
long sem_otime; /* 最后一次对信号量操作(semop)的时间 */
long sem_ctime; /* 对这个结构最后一次修改的时间 */
struct sem /*sem_base; /* 在信号量数组中指向第一个信号量的指针 */
struct sem_queue *sem_pending; /* 待处理的挂起操作*/
struct sem_queue **sem_pending_last; / * 最后一个挂起操作 */
struct sem_undo *undo; /* 在这个数组上的undo 请求 */
ushort sem_nsems; /* 在信号量数组上的信号量号 */
};
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) */
};
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* No. of semaphores in set */
};