相关函数列表
//管道 #include <unistd.h> int pipe(int fd[2]); //标准I/O库提供了两个函数,实现的操作是创建一个管道fork一个子进程关闭未 //使用的管道端,执行一个shell运行命令,然后等待命令终止 //type类似fopen函数,有"r","w"或者"rw"等 #include <stdio.h> FILE *popen(const char *cmdstring, const char *type); int pclose(FILE *fp); //FIFO有时也被称为命名管道,未命名的管道只能在两个相关进程之间使用,而且这两个相关的进程 //还要有一个共同的创建了它们的祖先进程,但FIFO不相关的进程也能交换数据 #include <sys/stat.h> int mkfifo(const char *path, mode_t mode); int mkfifoat(int fd, const char *path, mode_t mode); //下面函数提供的唯一服务就是由一个路径名和项目ID产生一个键 #include <sys/ipc.h> key_t ftok(const char *path, int id); //XSI IPC为每一个IPC结构关联了一个ipc_perm结构,该结构规定了权限和所有者,至少包含下面成员 struct ipc_perm { uid_t uid; //用户有效ID gid_t gid; //用户有效组ID uid_t cuid; //创建有效用户ID gid_t cgid; //创建有效组ID mode_t mode //访问模式 }; //消息队列 //每个队列都有一个msqid_ds结构与其相关联 struct msqid_ds { struct ipc_perm msg_perm; //权限所有者结构 msgqnum_t msg_qnum; //队列中有多少消息 msglen_t msg_qbytes; //队列的最大字节数 pid_t msg_lspid; //mgsend()的 pid pid_t msg_lrpid; //msgrcv()的 pid time_t msg_stime; //last msgsend() time time_t msg_rtime; //last-msgrcv() time time_t msg_ctime; //last-change time }; //打开一个现有的消息队列或创建一个新队列 #include <sys/msg.h> int msgget(key_t key, int flag); //操作队列,cmd参数指定对msqid指定的队列要执行的命令 //IPC_STAT 取此队列的msqid_ds结构,并将它存放在buf指向的结构中 //IPC_SET 将字段msg_perm.uid, msg_perm.gid, msg_perm.mode和 msg_qbytes从buf指向的结构 // 复制到与这个队列相关的msqid_ds结构中,此命令只能由下列两种进程执行:1)一种是其 // 有效用户ID等于msg_perm.cuid或msg_perm.uid, 2)是一种具有超级用户特权的进程,只 // 有超级用户才能增加msg_qbytes的值 //IPC_RMID 从系统中山川村该消息队列以及仍在该队列中的所有数据。这种数据立即生效。仍在使用 // 这一消息队列的其他进程再他们下一次试图对此队列进行操作时,将得到EIDRM错误。此 // 命令只能由下列两种进程执行: 1)一种是有效用户ID等于msg_perm.cuid或msg_perm.uid // 2)另一种是具有超级用户特权的进程 #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_id *buf); //调用下面函数将数据放到消息队列中 //flag参数的值可以指定为IPC_NOWAIT类似于文件I/O的非阻塞I/O标志, #include <sys/msg.h> int msgsend(int msqid, const void *ptr, size_t nbytes, int flag); //prt参数指向一个长整型。它包含了正的整型消息类型,其后紧接着的是消息数据 struct mymesg { long mtype; char mtext[512]; }; //从队列中取消息 //和msgsend一样,ptr参数指向一个长整型数,其后是存储实际消息数据的缓冲区,nbytes指定数据 //缓冲区的长度,若flag中设置了MSG_NOERROR,则消息会截断,参数type可以指定想要哪一种消息 //type==0 返回队列中的第一个消息 //type>0 返回队列中消息类型为type的第一个消息 //type<0 返回队列中消息类型值小于等于type绝对值的消息 #include <sys/msg.h> ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag); //消息队列 //每个队列都有一个msqid_ds结构与其相关联 struct msqid_ds { struct ipc_perm msg_perm; msgqunm_t msg_qnum; msglen_t msg_qbytes; pid_t msg_lspid; //pid of last msgsend() pid_t msg_lrpid; //pid of last msgrcv() time_t msg_stime; //last-msgsend() time time_t msg_rtime; //last-msgrcv() time time_t msg_ctime; //last-change time }; //信号量 //每个信号量由一个无名结构表示,它至少包含下列成员 struct { unsigned short semval; pid_t sempid; unsigned short semncnt; unsigned short semzcnt; }; //获得一个信号量ID #include <sys/sem.h> int semget(key_t key, int nsems, int flag); //用于操作信号量的函数,其中cmd参数是以下10种命令的一种: //IPC_STAT 对此集合取semid_ds结构,并存储在由arg.buf指向的结构中 //IPC_SET 按arg.buf指向的结构中的值设置与集合相关的结构中的sem_perm.uid等值 //IPC_RMID 从系统中删除该信号量集合 //GETVAL 返回成员semnum的semval值 //SETVAL 设置成员semnum的semval值 //GETPID 返回成员semnum的sempid值 //GETNCNT 返回成员semnum的semncnt值 //GETALL 取该集合中所有的信号量值 //SETALL 将该集合中所有的信号量都设置成arg.array指向的数组中的值 #include <sys/sem.h> int semctl(int semid, int semunm, int cmd, ...); //自动执行信号量集合上的从操作数组 #include <sys/sem.h> int semop(int semid, struct sembuf semoparray[], size_t nops); //参数semoparray是一个指针,它指向一个由sembuf结构表示的信号量操作数组 struct sembuf { unsigned short sem_num; short sem_op; short sem_flag; }; //共享内存 //内核为每个共享内存储段维护者一个结构,该结构至少要为每个共享存储段包含以下成员 struct shmid_ds { struct ipc_perm shm_perm; size_t shm_segsz; pid_t shm_lpid; pid_t shm_cpid; shmatt_t shm_nattch; time_t shm_atime; time_t shm_dtime; time_t shm_ctime; }; //获得一个共享存储的标识符 #include <sys/shm.h> int shmget(key_t key, size_t size, int flag); //对共享内存执行多种操作 //参数cmd指定下列5种命令中的一种,使其在shmid指定的段上执行 //IPC_STAT 取此段的shmid_ds结构,并将它存储在由buf指向的结构中 //IPC_SET 按buf指向的结构中的值设置与此共享存储段相关的shmid_ds结构中下一些字段 //IPC_RMID 从系统中删除该共享存储段 //SHM_LOCK 在内存中对共享存储段加锁,只能由超级用户执行 //SHM_UNLOCK 解锁共享存储段,只能由超级用户执行 #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf); //一旦创建了一个共享存储段,进程就看以调用shmat将其连接到它的地址空间中 //共享存储段连接到调用进程的哪个地址上与addr参数以及flag中是否指定SHM_RND位有关 //1.如果addr为0,则此段连接到由内核选择的第一个可用地址上,这是推荐的方式 //2.如果addr非0,并且没有指定SHM_RND,则此段连接到addr所指定的地址上 //3.如果addr非0,并且指定了SHM_RND,则此段连接到(addr-(addr mod SHMLBA))所表示的地址上, // SHM_RND命令的意思是取整,SHMLBA的意思是 低边界地址倍数 //如果flag中指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连接此段 #include <sys/shm.h> void *shmat(int shmid, const void *addr, int flag); //共享存储段的操作已经结束时,调用下面函数与该段分离,但这并不从系统中删除其标识符以及相关 //的数据结构,知道某个进程带IPC_RMID命令的调用shmctl特地的删除它为止 #include <sys/shm.h> int shmdt(const void *addr); //POSIX信号量 //使用下面函数来创建一个新的命名信号量或者使用一个现有信号量 #include <semaphore.h> sem_t *sem_open(const char *name, int oflag, .../* mode_t mode, unsigned int value */); //下面函数用来释放任何信号量相关的资源 #include <semaphore.h> int sem_close(sem_t *sem); //下面函数用来销毁一个命名信号量 #include <semaphore.h> int sem_unlink(const char *name); //使用下面函数用来实现信号量的减1操作,try函数会避免阻塞 #include <semaphore.h> #include <time.h> int sem_trywait(sem_t *sem); int sem_wait(sem_t *sem); int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict tsprt); //使用下面函数使信号量值增1,这和解锁一个二进制信号量或者释放一个计数信号量相关的资源过程 //是类似的 #include <semaphore.h> int sem_pos(sem_t *sem); //创建一个未命名的信号量 #include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); //对未命名信号量的使用已经完成时,可以调用下面函数丢弃它 #include <semaphore.h> int sem_destroy(sem_t *sem); //调用下面函数来检索信号量值 #include <semaphore.h> int sem_getvalue(sem_t *restrict sem, int *restrict valp);
进程间通讯(Inter Process Communication IPC)
UNIX系统IPC包括半双工管道,全双工管道,FIFO,XSI消息队列,XSI信号量,XSI共享存储,套接字
管道
管道是UNIX系统IPC的最古老形式,所有UNIX系统都提供此种通讯机制,管道有
下面两种限制
1)历史上他们是半双工的(即数据只能在一个方向上流动),限制某些系统提供全
双工的管道,但是为了最佳的可移植性我们决不预先假设系统支持全双工管道
2)管道只能在具有公共祖先的两个进程之间使用,通常一个管道由一个进程创建,
在进程调用fork之后,这个管道就能在父进程和子进程之间使用了
FOFO没有第二种局限性,UNIX域套接字没有这两种局限性
经由参fd返回两个文件描述符,fd[0]为读而打开,fd[1]为写而打开。 fd[1]的输出是fd[0]
对于一个从子进程到父进程的管道,父进程关闭fd[1],子进程关闭fd[0]
FIFO(命名管道)
当open一个FIFO时,非阻塞标志(O_NONBLOCK)会产生下列影响
1)在一般情况下(没有指定O_NONBLOCK),只读open要阻塞到某个其他进程为写而打开这个FIFO为止,
类似的,只写open要阻塞到某个其他进程为读而打开它为止
2)如果指定了O_NONBLOCK,则只读open立即返回。但是如果没有进程为读而打开一个FIFO,那么只写
open将返回-1,并将errno设置为ENXIO
FIFO有以下两种用途
1)shell命令使用FIFO将数据从一条管道传送到另一条时,无需创建中间临时文件
2)客户进程--服务器进程应用程序中,FIFO用作汇聚点,在客户进程和服务器进程二者之间传递数据
客户端和服务端通讯模式
XSI IPC
有三种XSI IP:消息队列,信号量以及共享存储
每个内核中的IPC结构(消息队列,信号量和共享内存)都有一个非负整数的 标示符(identifier)加以引用
如要向一个消息队列发送消息或者从一个消息队列取消息,只需要知道其队列标识符。
无论何时IPC结构都应指向一个键,这个键的数据类型是基本类型是系统数据类型key_t,包含在头文件
<sys/types.h>中
有多种方法使客户进程和服务器进程再同一IPC结构上汇聚
1)服务器进程可以指定键IPC_PRIVATE创建一个新IPC结构,将返回的标识符存放在某处(如一个文件)以便
客户进程取用。键IPC_PRIVATE保证服务器进程创建一个新IPC结构,这种技术的缺点: 文件系统操作需要
服务器进程将整型标识符写到文件中,此后客户进程又要读这个文件去的此标识符
IPC_PRIVATE键也可用于父进程子关系,父进程指定IPC_PRIVATE创建一个新IPC结构,所返回的标识符
可供fork后的子进程使用。接着子进程又可将此标识符为exec函数的一个参数传给一个新程序
2)可以在一个公用头文件中定义一个客户进程和服务器进程都认可的键。然后服务器进程指定此键创建一个
新的IPC结构,这种方式问题是该键可能与一个IP结构相结合,在此情况下get函数出错返回,服务器进程
必须处理这一错误,删除已存在的IPC结构然后试着再创建它
3)客户进程和服务器进程认同一个路径名和项目ID(项目ID是0--255之间的字符值)接着,调用函数fork将两个
值变换为一个键,然后在方法 2)中使用此键
XSI IPC权限
权限 | 位 |
用户读 | 0400 |
用户写(更改) | 0200 |
组读 | 0040 |
组写(更改) | 0020 |
其他读 | 0004 |
其他写(更改) | 0002 |
XSI IPC的问题
1)IPC结构是在系统范围内起作用,没有引用计数,如果进程创建了一个消息队列,并且在该队列中放入
几条消息然后终止,那么该消息内容不会被删除,他们会一直留在系统中直至发生下列动作
a)某个进程调用msgrcv或者msgctl读消息或删除消息队列
b)某个进程执行ipcrm命令删除消息队列
c)正在自举的系统删除消息队列
与管道消息,当最后一个引用管道的进程终止时,管道就被完全删除了,对FIFO而言,最后一个应用FIFO
的进程终止时,虽然FIFO的名字仍然保留在系统中直到被显示的删除,但是FIFO中的数据已被删除了
2)这些IPC结构在文件系统中没有名字,不能用文件I/O的方式去访问和修改他们。为了支持这些IPC对象,
内核中增加了十几个全新的系统调用(msgget,semop,shmat等)我们不能用ls 命令查看IPC对象,不能用
rm删除他们,于是又增加了新命令ipcs 和 ipcrm
因为这些形式的IP不使用文件描述符,所以不能对他们使用多路转换I/O,这使得它很难一次使用一个以上
这样的IPC结构
不同形式IPC之间的特征比较
IPC类型 | 无连接 | 可靠地 | 流控制 | 记录 | 消息类型或优先级 |
消息队列 | 否 | 是 | 是 | 是 | 是 |
STREAMS | 否 | 是 | 是 | 是 | 是 |
UNIX域套接字 | 否 | 是 | 是 | 否 | 否 |
UNIX域数据报套接字 | 是 | 是 | 否 | 是 | 否 |
FIFO(非STREAMS) | 否 | 是 | 是 | 否 | 否 |
信号量
信号量与已经介绍过的IPC结构不同,它是一个计数器,用于为多个进程提供对共享数据对象的访问
为获得共享资源,进程需要执行下列操作
1)测试控制该资源的信号量
2)若此信号量的值为正,则进程可以使用该资源,在这种情况下进程会将信号量值减1
3)否则若此信号量的值为0,则进程进入休眠状态,直至信号量大于0,进程被唤醒后返回步骤1)
常用的信号量形式为称为二元信号量(binary semaphore),它控制单个资源
遗憾的是XSI信号量与此相比要复杂的多,以下三种特性造成了这种不必要的复杂
1)信号量并非是单个非负值,而必需定义为含有一个或者多个信号量的集合,当创建信号量时,要指定集合
中信号量的数量
2)信号量的创建是独立于它的初始化的,这是一个致命的缺点,因为不能原子的创建一个信号量集合,并且
对该集合中的各个信号量赋初始值
3)即使么有进程正在使用各种形式的XSI IPC,它们仍然是存在的,有的程序在终止时并没有释放已经分配给
它们的信号量
/dev/zerio的存储映射
在读设备/dev/zero时,该设备是0字节无限资源,它也接收写向它的任何数据,但是又忽略这些数据,我们对此设备作为IPC的兴趣在于,当对其进行存储映射时,它具有一些特殊性质:
1)创建一个未命名的存储区,其长度是mmap的第二个参数,将其向上取整为系统的最近页长
2)存储区都初始化为0
3)如果多个进程的共同祖先进程对mmap指定了MAP_SHARED标志,则这些进程可共享此存储区
这种方式优点: 在调用mmap创建映射区之前,无需再一个实际文件。映射/dev/zero自动创建一个指定长度的
营社区。
这种方式缺点: 它只在两个相关进程之间起作用,但在相关进程之间使用线程可能更简单
进程间共享内存的方式
POSIX信号量接口意在解决XSI信号量接口的几个缺陷
1)相比于XSI接口,POSIX信号量接口考虑到了更高性能实现
2)POSIX信号量接口使用更简单,没有信号量集,在熟悉的文件系统操作后一些接口被模式化了,尽管没有
要求一定要在文件系统中实现,但是一些系统的的确是这么实现的
3)POSIX信号量在删除时表现更完美,回忆一下,当一个XSI信号量被删时信号量标识符会失败,使用POSIX
信号量时,操作能继续正常工作指导该信号量的最后一次引用被释放
POSIX信号量有两种形式: 命名的和未命名的
它们的差异在于创建和销毁的形式上,但其他工作一样
未命名信号量只存在内存中,并要求能使用心凉的进程必须可以访问内存,这意味着它们只能应用在同一
进程的线程,或不同进程中已映射相同内存内容直到它们的地址空间中的线程
命名信号量可以通过名字访问,因此可以被任何已知它们名字的进程中的线程使用
参考