Linux进程通信学习总结
http://blog.csdn.net/xiaoweibeibei/article/details/6552498
SYSV子系统的相关概念
引用标识符:引用标识符是一个整数,表示每一个SYSV子系统的对象(共享内存,信号量,消息队列),它用于访问对象是在系统中的传递。
键:在SYSV子系统中用于定位系统中的应用标识符,它相当于一种路由算法,用来决定如何访问一个SYSV子系统的对象。
ipc_perm结构:它对应于每一个进程通信机制的对象,其定义如下:
struct ipc_perm{
uid_t uid;// 所有者的有效用户ID
gid_t gid;//所有者的有效用户组ID
uid_t cuid;//创建者的有效用户ID
gid_t cgid;//创建者的有效用户组ID
mode_t mode;//表示此对象的访问权限
ulong seq;//对象的应用序号
key_t key;//对象的键
}
说明:
(1)mode:与文件访问权限不同的是,对于共享内存和消息队列的访问权限包括“可读”和“可写”,信号量的访问权限包括“可读”和“可改变”。
(2)可以使用ipcs命令查看进程间通信机制中对象的状态,该命令的参数有-m, -s和-q,分别表示查看共享内存,信号量和消息队列。使用默认参数时这三种状态都输出。
共享内存
共享内存通信是Linux系统中最底层的通信机制,也是最快速的通信机制,通过两个或多个进程共享同一块内存区域来实现进程间的通信。使用共享内存进行进程间通信时最重要的问题是如果解决进程的同步问题。使用共享内存的方法有两种,即:映射/dev/mem设备和内存映像。
shmid_ds结构:
struct shmid_ds{
struct ipc_perm shm_perm;//对应于该共享内存的ipc_perm结构;
int shm_segsz;//以字节表示共享内存的大小;
ushort shm_lkcnt;//共享内存区域被锁定的时间数;
pid_t shm_cpid;//创建该共享内存的进程ID;
pid_t shm_lpid;//最忌一次调用shmop函数的进程ID;
ulong shm_nattch;//当前使用该共享内存区域的进程数;
time_t shm_atime;//最近一次附加操作的时间;
time_t shm_dtime;//最近一次分离操作的时间;
time_t shm_ctime;//最近一次改变的时间;
}
共享内存的创建与打开
函数:int shmget(key_t key,int size, int falg);
头文件:sys/types.h sys/ipc.h sys/shm.h
函数作用:用于创建一个新的共享内存或打开一个已经存在的共享内存;
函数说明:
key:表示所创建或打开的共享内存的键;
size:表示共享内存区域的大小,只在创建一个新的共享内存时有效;
flag:表示调用函数的操作类型,也用于表示共享内存的访问权限,两只通过逻辑或表示。
调用函数的作用由key和flag决定:具体如下:
key为IPC_PRIVATE:表示创建一个新共享内存,flag取值无效;
key不为IPC_PRIVATE,flag为IPC_CREATE 而没有设定IPC_EXCEL位:操作由key决定,如果key是已经存在,则为打开操作,不存在为创建操作;
key不为IPC_PRIVATE,flag而没有IPC_CREATE和IPC_EXCEL位:只执行创建共享内存操作,且参数key值不能与内核中已经存在的任何共享内存的键值相同,否则调用失败;
函数调用成功,返回为共享内存的引用标识符,调用失败时,返回值为-1。
附加
函数:void* shmat(int shmid,void *addr,int flag);
头文件:sys/types.h sys/ipc.h sys/shm.h
函数说明:
shmid表示要附加的共享内存的引用标识符;flag表示shmat函数的操作方式。其中参数addr和flag共同决定共享内存区域要附加到的地址值,一般为0。
Addr为0,系统自动查找进程地址空间,将共享内存区域附加到第一块有效内存区域上,flag此时无效;
addr不为0,而flag没有设置SHM_RND位,则,共享内存区域附加到由addr指定的地址处;
addr不为0,而flag设置了SHM_RND位,则共享内存区域附加到有addr(addr mod SHMLBA)计算得到的地址处;
SHM_RND意为取整,SHMLAB意为低边界地址的倍数,它总是2的乘方,该算式是将地址向下取最近一个SHMLAB的倍数,一般而言不需要设定SHM_RND,如果需要以只读方式连接恭喜那个段,需要设定flag为SHM_RDONLY位。
Shmat函数返回值是该段所连接的实际地址值,如果出错返回(void *)-1,调用成功为共享内存区域的指针。
分离
函数:int shmdt(void *addr);
头文件:sys/types.h sys/ipc.h sys/shm.h
函数说明:
shmdt的调用可以使共享内存与该进程的地址分离,但不删除恭喜那个内存本身,addr为要分离共享内存区的指针,也就是调用shmat函数的返回值,调用成功时返回0,失败返回-1;
共享内存的控制
函数:int shmctl( int shmid,int cmd,struct shmid_ds *buf);
头文件:sys/types.h sys/ipc.h sys/shm.h
函数说明:
参数shmid为共享内存的引用标识符。参数cmd表示调用该函数希望执行的操作,其取值如下:
SHM_L0CK:将共享内存区域上锁,只能由超级用户执行;
IPC_RMID:用于删除 共享内存。执行该命令后,共享内存的应用标识符将立即被删除,该共享内存不能再被其它进程所附加。但是改共享内存的真正删除要等所有已经附加到该共享内存的结束或断开与该共享内存的连接后才执行;
IPC_SET:按参数buf指向的结构中的值设定该共享内存对应的shmid_ds结构,只有有效用户ID和共享内存的所有者或创建者ID相同的用户进程,以及超级用户进程才能执行这一操作;
IPC_STAT:用于取得该恭喜那个内存的shmid_ds结构,保存于buf指向的缓冲区;
SHM_UNLOCK:将上锁的共享内存区释放,只能由超级用户执行。
信号量机制
信号量是一种用于对多个进程访问共享资料进行控制的一种机制,主要是为了解决互斥共享资源的同步问题的一种机制。当有进程要访问某一资源时候,首先检查该资源的信号量,如果大于1,则可以使用,同时使信号量减1,当访问结束后,信号量在加1,当信号量为0时,进程休眠,直到信号量的值大于0时,进程被唤醒。
双态信号量:初始值为1,任意时刻最多只有一个进程可以访问。
不能定义一个信号量,只能定义一个信号量集,同一信号量集只能使用同一引用ID,每一个信号量集都只有一个与其对应的结构,该结构定义如下:
struct semid_ds{
struct ipc_perm sem_perm;//信号量集的ipc_perm结构
struct sem *sem_base;//指向信号量集的第一个信号量的sem结构
ushort sem_nsems;//信号量集中信号的个数
time_t sem_otime;//最近一次调用semop的时间
time_t sem_ctime;//最近一次改变该信号量集的时间
}
sem结构记录了一个信号量的信息,其定义如下:
struct sem{
ushort semval;//信号量的值
pid_t sempid;//最近一次访问资源的的进程ID
ushort semncnt;//等待可利用资源出现的进程数
ushort semzcnt;//等待全部资源可被独占的进程数
}
信号量集的创建与打开
函数:int semget(key_t key,int nsems,int flag);
头文件:sys/types.h sys/ipc.h sys/shm.h
函数说明:
该函数用于创建或打开一个已经存在的信号量集,其参数和shmget功能类似。当该函数调用成功时,返回信号量集的引用标识符,调用失败返回-1;当调用该函数创建一个信号量时,其相应的semid_ds被初始化。
信号量的操作
函数:int semop(int semid,struct sembuf semoparray[],size_t nops);
头文件:sys/types.h sys/ipc.h sys/shm.h
函数说明:
semid为信号量集的引用ID;semoparray指定调用semop的操作,nops指定semoparray的元素个数。semoparray的每个元素表示一个操作,该函数是一个原子操作,一旦执行就将执行数组中的所有操作
struct sembuf结构
struct sembuf{
ushort sem_num;//指定要操作的信号量
short sem_op;//所要执行的操作
short sem_flag;//操作标记,取值有IPC_NOWAIT和SEM_UNDO
}
sem_op说明:
sem_op>0:表示该资源使用完毕,交回该资源。此时信号量集的semid_ds结构的sem_base.semval将加上sem_op的绝对值。若此时设置了SEM_UNDO位,该信号量的调整值将减去sem_op的值;
sem_op=0:表示进程要等待,直至sem_base.semval表为0;
sem_op<0:表示进程希望使用该资源。此时将比较sem_base.semval和sem_op的绝对值的大小。如果sem_base.semval大于sem_op的绝对值,说明资源足够分配给此进程,则sem_base.semval将减去sem_op的绝对值。若此时设置了SEM_UNDO位,则信号量的调整值将加上sem_op的绝对值。如果sem_base.semval小于sem_op的绝对值,表示资源不足。若设置了IPC_NOWAIT位,则函数出错返回,否则semid结构中的sem_base.semncnt加1,进程等待直至sem_base.semval大于等于sem_op的绝对值或该信号量被删除。
信号量的控制
函数: int semctl(int semid, int semnum,int cmd,union semun arg);
头文件:sys/types.h sys/ipc.h sys/shm.h
函数说明:semid是要引用信号量集的引用标识符,semmum用于指明某个特定信号量,cmd为要执行的操作。
union semun结构
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo * __buf;//linux特有
}
说明:
该联合体中各个变量的使用情况与参数cmd有关,具体如下:
GETALL:获取semid所表示的信号量集中信号量的数量,并将该值存放在无符号短整数arg.array中;
GETNCNT:获取semid所表示的信号量集中等待给定信号量锁定进程数目,即semid_ds结构中sem.semncnt的值;
GETPID:获取semid所表示的信号量集中最后一个使用semop函数的进程ID,即semid_ds结构中sem.sempid的值;
GETVAL:获取semid所表示的信号量集中semnum所指定信号量的值;
GETZCNT:获取semid所表示的信号量集中等待信号量为0的进程数目,即semid_ds结构中sem.semncnt的值;
IPC_RMID:删除该信号量;
IPC_SET:按参数arg.buf指向的结构体中的值设置该型号量对应的semid_ds结构。只有有效用户ID和信号量的所有者ID或创建者ID相同的用户进程,以及超级用户进程可以执行这一操作;
IPC_STAT:获取该信号量的semid_ds结构,保存在arg.buf指向的缓冲区中;
SETALL:以arg.array中的值设置semid所表示的信号量集中信号量的个数;
SETVAL:设置semid所表示的信号量集中semnum所指定信号量的值;
管道
当管道建立后,产生两个文件描述符,一个用于读取,一个用于写入,他只能用于两个进程之间的通信,且这两个进程必须有同一个进程所派生,即:这两个进程必须是同源进程。管道的通信是半双工的,只允许单方向的传输数据。由于管道类似于文件操作,因此,当管道建立后,就可以采用文件操作的方式来对管道进行读取。管道不是一个真实存在的文件,他只在内核中存储,不存在于文件系统中。在管道的读写工程中,要注意分清是数据传输暂时无数据读取还是数据传输结束。
管道的创建
函数:int pipe(int filedescriptors[2]);
头文件:unistd.h
函数数名:filedescriptors[0]表示读取端文件描述符,filedescryipto-
rs[1]用于写入端文件描述符,调用成功返回0,失败返回-1。当调用哪个pipe创建一个通道后 ,进程只能将文件描述符传递给子进程方才可以实现通信,较为常用的做法是调用exec函数使子进程执行所需程序,然后根据数据传输方向关闭父进程和子进程的文件描述符。
命名管道
命令管道又叫先进先出管道,其存在于文件系统中,是一种特殊的管道,其特点有:
l 可以用于任何两个进程之间通信;
l 由于命名管道存在于文件系统中,当进程对命名管道使用结束后,其依然存在于文件系统中,除非对其进行了删除操作;
l 命名管道仍只能用于单向数据传输,如果用命名管道实现两个进程间通信数据段相互交换,需要使用两条命名管道。
命名管道的创建
函数: (1) int mkefifo(const char *pathname,mode_t mode);
(2) int mknod(char *pathname,mode_t mode,dev_t dev);
头文件:sys/types.h sys/stat.h
函数说明:pathname用于存放命名管道的文件名,mode用于指定所创建文件的权限,调用成功返回0,失败返回-1;mknod不是专用于创建管道的函数,当dev为0 时,表示创建一个命名管道;
创建命令管道的shell命令: mkfifo FIFO_TEST mknod FIFO+TEST p
命名管道使用注意事项:
l 创建命名管道后,它只存在于文件系统,如果要使用,需要在进程中打开,如果没有进程写打开一个命名管道,就对其进行写操作,将产生SIGPIPE信号,write函数将errno设置为出错号;
l 系统定义的常数PIPE_BUF规定了命名管道缓冲的大小,当写入数据超过规定大小时,将会使数据发生交错;
l 当一个进程从命名管道中读取数据时,如果命名管道中的全部数据读写完了,则read函数认为读到文件末尾,返回值为0,但又可能数据段写入并没结束,写入命名管道的进程还有数据要传输。一次,要分清是数据传输结束还是暂时无数据读取,如果是后者,则必须让读取数据的进程等待。
技巧:
使用命名管道时,需要在两个将进行通信的进程中分别打开命名管道,因此,当一个读打开(或写开)一个命名管道而没有其他进程写打开(或读打开)此命名管道时,该进程就会进入阻塞状态,直到另外一个进程写打开(或读打开)此命名管道。
删除命名管道函数:int unlink(const char* pathname);
头文件: unistd.h
消息队列
消息队列是一系列连续排列的消息,保存在内核中,通过消息队列的引用标识符来访问。其与管道有些相似,但其优点是每个消息指定消息类型,接受消息的进程可以请求介绍下一条消息,也可以请求接受下一条特定的消息。
消息队列有两部分组成,即:消息类型和所传递的数据。一般用结构来表示。
msgid_ds结构:
struct msgid_ds{
struct ipc_perm msg_perm;//对应于消息队列的ipc_perm结构指针;
struct msg *msg_first;//指向消息队列中的第一个消息;
struct msg *msg_last;//指向消息队列中的最后个消息;
ulong msg_types;//记录消息队列中当前的总字节数;
ulong msg_gnum;//记录消息队列中当前的总消息数;
ulong msg_qbytes;//记录消息队列中最大可容纳的字节数;
pid_t msg_lspid;//最近一次执行msgsnd函数的进程的ID;
pid_t msg_lrpid;//最近一次执行msgrcv函数的进程的ID;
time_t msg_stime;//最近一次执行msgsnd函数的时间;
time_t msg_rtime;//最近一次执行msgrcv函数的时间;
time_t msg_ctime;//最近一次改变该消息队列的时间;
}
消息队列的创建与打开
函数: int msgget(key_t key, int flag);
头文件: sys/types.h sys/ipc.h sys/msg.h
函数说明:和共享内存的创建相似;
消息的发送
函数: int msgsnd( int msgid, const void *ptr, size_t nbytes, int flag);
头文件:sys/types.h sys/ipc.h sys/msg.h sys/msg.h
函数说明:该消息将添加消息到队尾,msgid为消息队列引用标识符,ptr是指向要发送的消息的指针,nbytes记录发送消息中数据的长度,flag为指定消息队列满时的处理方法。如果设定了IPC_NOWAIT位,则立即出错返回,否则发送消息的进程被阻塞,直至消息队列中有空间或该消息队列被删除时候返回。调用成功时,返回0;失败时,返回-1。
消息的接收
函数:int msgrcv( int msgid,void *ptr,size_t nbytes,long types,int flag);
头文件:sys/types.h sys/ipc.h sys/msg.h
函数说明:ptr为接受消息的缓冲区,当函数调用成功时,返回值为接收到消息的长度,以字节计,失败时返回-1。
type含义如下:
type 取值 |
含义 |
0 |
接收队列第一条消息 |
>0 |
接收队列中类型值等于type的第一条消息 |
<0 |
接收队列中类型值小于等于type绝对值中,类型值最小的消息中第一条消息 |
flag取值:
IPC_NOWAIT:指定type位无效时的处理方法,如果被设置,,则立即出错返回,否则接收消息的进程被阻塞,直至type位有效或该消息队列被删除。
MSG_NOERROR:用于设置消息长度大于nbytes时的方法,如果该位被设置,当消息长度大于nbytes时,超出部分被截断,否则,不接收该消息,将其保留在消息队列中,出错返回。
消息队列的控制
函数: int msgctl(int msgid,int cmd,struct msgid_ds *buf);
头文件:sys/types.h sys/ipc.h sys/msg.h
函数说明:msgid为消息队列的引用标识符,其中cmd取值如下:
IPC_RMID:删除消息队列。如果有进程对此消息队列进行操作,则出错返回。只有有效用户ID,消息队列所有者ID或与创建者ID相同的用于进程,已经超级管理员用户进程可以执行该操作;
IPC_SET:按参数buf指向的结构中的值设置该消息队列对应的msgid_ds结构。只有有效用户ID,消息队列所有者ID或与创建者ID相同的用于进程,已经超级管理员用户进程可以执行该操作;
IPC_STAT:获取该消息队列的msgid_ds结构,保存于buf所指向的缓冲区。