进程线程间通信.docx
- 文档编号:17731213
- 上传时间:2023-04-24
- 格式:DOCX
- 页数:34
- 大小:34.51KB
进程线程间通信.docx
《进程线程间通信.docx》由会员分享,可在线阅读,更多相关《进程线程间通信.docx(34页珍藏版)》请在冰豆网上搜索。
进程线程间通信
Key的创建与ftok
在SystemV中,我们经常用用key_t的值来创建或者打开信号量,共享内存和消息队列。
这个在IPC的环境中十分的重要,比如说,服务器创建了一个消息队列,等待客户机发送请求。
那么如何创建或者打开已有的消息队列呢?
一般而言,我们对于服务器使用的路径和项目id(proj_id)是已知的,所以客户机可以获取相同的key来打开消息队列并进行操作。
下面就是ftok的使用原型:
#include
#include
key_tftok(constchar*pathname,intproj_id);
注意:
1)pathname一定要在系统中存在
2)pathname一定是使用进程能够访问的
3)proj_id是一个1-255之间的一个整数值,典型的值是一个ASCII值。
当成功执行的时候,一个key_t值将会被返回,否则-1被返回。
我们可以使用errno来确定具体的错误信息,如果我们很懒惰的话,不妨就使用perror函数来答应对应的出错字符信息。
下面的程序简单的演示和打印如何使用ftok及其对应值
#include
#include
#include
intmain(void)
{
for(inti=1;i<256;++i)
printf("key=%ul\n",ftok("/tmp",i ));
return0;
}
消息队列
1)intmsgget(key_tkey,intmsgflg)
参数key是一个键值,由ftok获得;msgflg参数是一些标志位。
该调用返回与健值key相对应的消息队列描述字。
在以下两种情况下,该调用将创建一个新的消息队列:
●如果没有消息队列与健值key相对应,并且msgflg中包含了IPC_CREAT标志位;
●key参数为IPC_PRIVATE;
参数msgflg可以为以下:
IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或结果。
调用返回:
成功返回消息队列描述字,否则返回-1。
注:
参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列,只意味着即将创建新的消息队列。
2)intmsgrcv(intmsqid,structmsgbuf*msgp,intmsgsz,longmsgtyp,intmsgflg);
该系统调用从msgid代表的消息队列中读取一个消息,并把消息存储在msgp指向的msgbuf结构中。
msqid为消息队列描述字;消息返回后存储在msgp指向的地址,msgsz指定msgbuf的mtext成员的长度(即消息内容的长度),msgtyp为请求读取的消息类型;读消息标志msgflg可以为以下几个常值的或:
●IPC_NOWAIT如果没有满足条件的消息,调用立即返回,此时,errno=ENOMSG
●IPC_EXCEPT与msgtyp>0配合使用,返回队列中第一个类型不为msgtyp的消息
●IPC_NOERROR如果队列中满足条件的消息内容大于所请求的msgsz字节,则把该消息截断,截断部分将丢失。
msgrcv手册中详细给出了消息类型取不同值时(>0;<0;=0),调用将返回消息队列中的哪个消息。
msgrcv()解除阻塞的条件有三个:
●消息队列中有了满足条件的消息;
●msqid代表的消息队列被删除;
●调用msgrcv()的进程被信号中断;
调用返回:
成功返回读出消息的实际字节数,否则返回-1。
3)intmsgsnd(intmsqid,structmsgbuf*msgp,intmsgsz,intmsgflg);
向msgid代表的消息队列发送一个消息,即将发送的消息存储在msgp指向的msgbuf结构中,消息的大小由msgze指定。
对发送消息来说,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。
造成msgsnd()等待的条件有两种:
●当前消息的大小与当前消息队列中的字节数之和超过了消息队列的总容量;
●当前消息队列的消息数(单位"个")不小于消息队列的总容量(单位"字节数"),此时,虽然消息队列中的消息数目很多,但基本上都只有一个字节。
msgsnd()解除阻塞的条件有三个:
●不满足上述两个条件,即消息队列中有容纳该消息的空间;
●msqid代表的消息队列被删除;
●调用msgsnd()的进程被信号中断;
调用返回:
成功返回0,否则返回-1。
4)intmsgctl(intmsqid,intcmd,structmsqid_ds*buf);
该系统调用对由msqid标识的消息队列执行cmd操作,共有三种cmd操作:
IPC_STAT、IPC_SET、IPC_RMID。
●IPC_STAT:
该命令用来获取消息队列信息,返回的信息存贮在buf指向的msqid结构中;
●IPC_SET:
该命令用来设置消息队列的属性,要设置的属性存储在buf指向的msqid结构中;可设置属性包括:
msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同时,也影响msg_ctime成员。
●IPC_RMID:
删除msqid标识的消息队列;
调用返回:
成功返回0,否则返回-1
消息队列的限制:
每个消息队列的容量(所能容纳的字节数)都有限制,该值因系统不同而不同。
每个消息队列所能容纳的最大消息数:
在redhad8.0中,该限制是受消息队列容量制约的:
消息个数要小于消息队列的容量(字节数)。
另一个限制是系统中最大消息队列的个数。
共享内存
打开创建存储段:
intshmget(key_tkey,size_tsize,intshmflg);
返回:
失败-1,成功返回非负的共享存储段id
第一个参数key是共享存储关键字。
它有特殊值IPC_PRIVATE表示总是创建一个进程私有的共享存储段。
当key值不等于IPC_PRIVATE时,shmget动作取决于最后一个参数shmflg标志:
1.IPC_CREAT单独设置此标志,当系统中不存在相同key时,创建一个新的共享存储段,否则返回已存在的共享存储段。
2.IPC_EXCL单独设置不起作用。
与IPC_CREAT同时设置时,当系统中存在相同key时,错误返回。
保证不会打开一个已存在的共享存储段。
如果没有指定IPC_CREATE并且系统中不存在相同key值的共享存储段,将失败返回。
第三个参数也可以设置共享存储段的访问权限,用或于上面的值操作。
第二个参数size指明要求的共享存储段的大小。
当key指定的共享存储段已存在时,取值范围为0和已存在共享段大小。
或简单指定为0。
成功后此函数返回共享存储段id,同时创建于参数key相连的数据结构shmid_ds。
此节构为系统内部使用。
存储段控制函数:
可获得shmid_ds全部内容
intshmctl(intshmid,intcmd,structshmid_ds*buf);
返回:
失败-1并置errno,成功返回0
第一个参数,必须由shmget返回的存储段的id。
cmd为指定要求的操作。
CMD说明
IPC_STAT放置与shmid相连的shmid_ds结构当前值于buf所指定用户区
IPC_SET用buf指定的结构值代替与shmid相连的shmid_ds结构值
IPC_RMID删除制定的信号量集合
SHM_LOCK锁住共享存储段。
只能由超级管理员使用
SHM_UNLOCKunlock共享存储段。
只能由超级管理员使用
存储段连接:
void*shmat(intshmid,constvoid*shmaddr,intshmflg);
返回:
失败-1并置errno,成功返回连接的实际地址
第一个参数,必须由shmget返回的存储段的id
第二个参数指明共享存储段要连接到的地址。
0,系统为我们创建一个适当的地址值。
否则自己指定。
第三个参数shmflg可以设成:
SHM_RND,SHM_RDONLY。
SHM_RND为自己指定连接地址时用。
SHM_RDONLY说明此存储段只读。
存储段分离:
intshmdt(constvoid*shmaddr);
返回:
失败-1并置errno,成功返回0
分离由shmaddr指定的存储段。
此值应该由shmat返回的值。
此函数不是删除共享存储段,而是从当前进程分离存储段。
当进程推出时,系统会自动分离它连接的所有共享段。
/*****testwrite.c*******/
#include
#include
#include
#include
typedefstruct{
charname[4];
intage;
}people;
Voidmain(intargc,char**argv)
{
intshm_id,i;
key_tkey;
chartemp;
people*p_map;
char*name="/dev/shm/myshm2";
key=ftok(name,0);
if(key==-1)perror("ftokerror");
shm_id=shmget(key,4096,IPC_CREAT);
if(shm_id==-1)
{
perror("shmgeterror");
return;
}
p_map=(people*)shmat(shm_id,NULL,0);
temp='a';
for(i=0;i<10;i++){
temp+=1;
memcpy((*(p_map+i)).name,&temp,1);(*(p_map+i)).age=20+i;
}
if(shmdt(p_map)==-1)
perror("detacherror");
}
/**********testread.c************/
#include
#include
#include
#include
typedefstruct{
charname[4];
intage;}people;
main(intargc,char**argv)
{
intshm_id,i;
key_tkey;
people*p_map;
char*name="/dev/shm/myshm2";
key=ftok(name,0);if(key==-1)
perror("ftokerror");
shm_id=shmget(key,4096,IPC_CREAT);
if(shm_id==-1)
{
perror("shmgeterror");
return;
}
p_map=(people*)shmat(shm_id,NULL,0);
for(i=0;i<10;i++){printf("name:
%s\n",(*(p_map+i)).name);
printf("age%d\n",(*(p_map+i)).age);
}
if(shmdt(p_map)==-1)
perror("detacherror");
}
管道、命名管道
管道的局限性
管道的主要局限性正体现在它的特点上:
●只支持单向数据流;
●只能用于具有亲缘关系的进程之间;
●没有名字;
●管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);
●管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等;
有名管道
管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道(namedpipe或FIFO)提出后,该限制得到了克服。
FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。
这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。
值得注意的是,FIFO严格遵循先进先出(firstinfirstout),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。
它们不支持诸如lseek()等文件定位操作。
有名管道的打开规则:
有名管道比管道多了一个打开操作:
open。
如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。
如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。
有名管道的读写规则
从FIFO中读取数据:
约定:
如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。
如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。
对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。
对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:
当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。
解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。
读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。
如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。
注:
如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。
向FIFO中写入数据:
约定:
如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。
对于设置了阻塞标志的写操作:
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
对于没有设置阻塞标志的写操作:
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
在写满所有FIFO空闲缓冲区后,写操作返回。
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;
注意:
利用命名管道FIFOPIPE进行通信时,读者在打开管道后(无论阻塞或非阻塞),若关闭管道,则会丢失管道中的所有消息。
例子:
intcreateAndOpenMsg_NBR(char*var)//非阻塞打开命名管道
{
intfifo_fd;
if(var!
=NULL){
if(mkfifo(var,O_CREAT|O_EXCL|0666)<0)
{
if(errno!
=EEXIST){
fprintf(stderr,"\nmkfifo%serror:
%s\n\n",var,strerror(errno));
return-1;
}
//printf("mkfifoexist!
\n");
}
}
else{
return-1;
}
if((fifo_fd=open(FIFO_VAR,O_RDONLY|O_NONBLOCK))<0)
{
if(errno==ENXIO){
fprintf(stderr,"openerrorfornodata:
%s\n",strerror(errno));
returnfifo_fd;
}
else{
fprintf(stderr,"openerror:
%s\n",strerror(errno));
return-1;
}
}
returnfifo_fd;
}
intrecvMsgFromControl(MSG_STRUC*msg,intfifo_fd)//从管道读取数据
{
inthr=0;
if((hr=read(fifo_fd,msg,sizeof(MSG_STRUC)))<=0)
{
if(hr<0){
if(errno==EAGAIN){
fprintf(stderr,"readerror,tryagain:
%s\n",strerror(errno));
return0;
}
else{
fprintf(stderr,"readerror:
%s\n",strerror(errno));
return-1;
}
}
else{
//printf("readfindnodata\n");
return0;
}
}
//close(fifo_fd);
return1;
}
intsendToControl(MSG_STRUC*msg)//向管道写入数据
{
intfifo_fd;
if(mkfifo(FIFO_VAR,O_CREAT|O_EXCL|0666)<0)
{
if(errno!
=EEXIST){
fprintf(stderr,"\nmkfifoerror:
%s\n\n",strerror(errno));
return-1;
}
//printf("mkfifoexist!
\n");
}
if((fifo_fd=open(FIFO_VAR,O_RDWR))<0)
{
if(errno==ENXIO){
fprintf(stderr,"openerrorfornodata:
%s\n",strerror(errno));
return0;
}
else{
fprintf(stderr,"openerror:
%s\n",strerror(errno));
return-1;
}
}
if(write(fifo_fd,msg,sizeof(MSG_STRUC))<=0)
{
if(errno==EAGAIN){
fprintf(stderr,"writeerror,tryagain:
%s\n",strerror(errno));
close(fifo_fd);
return0;
}
else{
fprintf(stderr,"writeerror:
%s\n",strerror(errno));
close(fifo_fd);
return-1;
}
}
close(fifo_fd);
return0;
}
信号量
1。
POSIX无名信号量
如果你学习过操作系统,那么肯定熟悉PV操作了.PV操作是原子操作.也就是操作是不可以中断的,在一定的时间内,只能够有一个进程的代码在CPU上面执行.在系统当中,有时候为了顺利的使用和保护共享资源,大家提出了信号的概念.假设我们要使用一台打印机,如果在同一时刻有两个进程在向打印机输出,那么最终的结果会是什么呢.为了处理这种情况,POSIX标准提出了有名信号量和无名信号量的概念,由于Linux只实现了无名信号量,我们在这里就只是介绍无名信号量了.信号量的使用主要是用来保护共享资源,使的资源在一个时刻只有一个进程所拥有.为此我们可以使用一个信号灯.当信号灯的值为某个值的时候,就表明此时资源不可以使用.否则就表>示可以使用.为了提供效率,系统提供了下面几个函数
POSIX的无名信号量的函数有以下几个:
#include
intsem_init(sem_t*sem,intpshared,unsignedintvalue);
intsem_destroy(sem_t*sem);
intsem_wait(sem_t*sem);
intsem_trywait(sem_t*sem);
intsem_post(sem_t*sem);
intsem_getvalue(sem_t*sem);
sem_init创建一个信号灯,并初始化其值为value.pshared决定了信号量能否在几个进程间共享.由于目前Linux还没有实现进程间共享信号灯,所以这个值只能够取0.sem_destroy是用来删除信号灯的.sem_wait调用将阻塞进程,直到信号灯的值大于0.这个函数返回的时候自动的将信号灯的值的件一.sem_post和sem_wait相反,是将信号灯的内容加一同时发出信号唤醒等待的进程..sem_trywait和sem_wait相同,不过不阻塞的,当信号灯的值为0的时候返回EAGAIN,表示以后重试.sem_getvalue得到信号灯的值.
2。
SystemV信号量
SystemV信号量的函数主要有下面几个.
#include
#include
#include
structsembuf{
shortsem_num;/*使用那一个信号*/
shortsem_op;/*进行什么操作*/
shortsem_flg;/*操作的标志*/
};
key_tftok(char*pathname,charproj);
ftok函数是根据pathname和proj来创建一个关键字..
intsemget(k
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 进程 线程 通信