实验三进程间通信 1.docx

上传人:b****8 文档编号:9122541 上传时间:2023-05-17 格式:DOCX 页数:23 大小:101.31KB
下载 相关 举报
实验三进程间通信 1.docx_第1页
第1页 / 共23页
实验三进程间通信 1.docx_第2页
第2页 / 共23页
实验三进程间通信 1.docx_第3页
第3页 / 共23页
实验三进程间通信 1.docx_第4页
第4页 / 共23页
实验三进程间通信 1.docx_第5页
第5页 / 共23页
实验三进程间通信 1.docx_第6页
第6页 / 共23页
实验三进程间通信 1.docx_第7页
第7页 / 共23页
实验三进程间通信 1.docx_第8页
第8页 / 共23页
实验三进程间通信 1.docx_第9页
第9页 / 共23页
实验三进程间通信 1.docx_第10页
第10页 / 共23页
实验三进程间通信 1.docx_第11页
第11页 / 共23页
实验三进程间通信 1.docx_第12页
第12页 / 共23页
实验三进程间通信 1.docx_第13页
第13页 / 共23页
实验三进程间通信 1.docx_第14页
第14页 / 共23页
实验三进程间通信 1.docx_第15页
第15页 / 共23页
实验三进程间通信 1.docx_第16页
第16页 / 共23页
实验三进程间通信 1.docx_第17页
第17页 / 共23页
实验三进程间通信 1.docx_第18页
第18页 / 共23页
实验三进程间通信 1.docx_第19页
第19页 / 共23页
实验三进程间通信 1.docx_第20页
第20页 / 共23页
亲,该文档总共23页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

实验三进程间通信 1.docx

《实验三进程间通信 1.docx》由会员分享,可在线阅读,更多相关《实验三进程间通信 1.docx(23页珍藏版)》请在冰点文库上搜索。

实验三进程间通信 1.docx

实验三进程间通信1

实验三线程控制和进程间通信

一、实验目的

通过Linux管道通信机制、消息队列通信机制的使用,加深对不同类型的进程通信方式的理解。

二、实验内容:

1.熟悉Linux的管道通信机制

2.熟悉Linux的消息队列通信机制

三、思考

1.有名管道和无名管道之间有什么不同?

2.管道的读写与文件的读写有什么异同?

3.Linux消息队列通信机制中与教材中的消息缓冲队列通信机制存在哪些异同?

四、实验指导

<一>Linux管道通信机制

管道是所有UNIX都提供的一种进程间通信机制,它是进程之间的一个单向数据流,一个进程可向管道写入数据,另一个进程则可以从管道中读取数据,从而达到进程通信的目的。

1.无名管道

无名管道通过pipe()系统调用创建,它具有如下特点:

(1)它只能用于具有亲缘关系的进程(如父子进程或者兄弟进程)之间的通信。

(2)管道是半双工的,具有固定的读端和写端。

虽然pipe()系统调用返回了两个文件描述符,但每个进程在使用一个文件描述符之前仍需先将另一个文件描述符关闭。

如果需要双向的数据流,则必须通过两次pipe()建立起两个管道。

(3)管道可以看成是一种特殊的文件,对管道的读写与文件的读写一样使用普通的read、write等函数,但它不是普通的文件,也不属于任何文件系统,而只存在于内存中。

2.pipe系统调用

(1)函数原型

#include

intpipe(intfiledes[2]);

(2)参数

filedes参数是一个输出参数,它返回两个文件描述符,其中filedes[0]指向管道的读端,filedes[1]指向管道的写端。

(3)功能

pipe在内存缓冲区中创建一个管道,并将读写该管道的一对文件描述符保存在filedes所指的数组中,其中filedes[0]用于读管道,filedes[1]用于写管道。

(4)返回值

成功返回0;失败返回-1,并在error中存入错误码。

(5)错误代码

EMFILE:

进程使用的文件描述符过多

ENFILE:

系统文件表已满

EFAULT:

非法参数filedes

3.无名管道的阻塞型读写

管道缓冲区有4096B的长度限制,因此,采用阻塞型读写方式时,当管道已经写满时,写进程必须等待,直到读进程取走信息为止。

同样,读空的管道时,也可能会引起进程阻塞。

当管道大小(管道缓冲区中待读的字节数)为p,而用户进程请求读n个字节时:

(1)若不存在写进程:

①p=0,则返回0;

②0

③p≥n,则读得n个字节,返回n,管道缓冲区中还剩p-n个字节;

(2)若存在写进程,且写进程没因写管道而阻塞时:

①p=0,读进程阻塞等待数据被写入管道;

②0

③p≥n,则读得n个字节,返回n,管道缓冲区中还剩p-n个字节;

(3)若存在写进程,且写进程因写管道而阻塞时:

①0

最后,返回实际读得的字节数;

②p≥n,则读得n个字节,返回n,管道缓冲区中还剩p-n个字节。

当管道缓冲区中有u个字节未用,而用户进程请求写入n个字节时:

(1)若不存在读进程,则向写管道的进程将发SIGPIPE信号,并返回-EPIPE。

(2)若存在至少一个读进程:

①u<n≤4096则写进程等待,直到有n-u个字节被释放为止,写入n个字节,返回n;

②n>4096则写入n个字节(必要时等待)并返回n;

③u≥n写入n个字节,返回n。

4.无名管道使用实例

//pipeDemo.c

#include

#include

#include

#include

#defineBUFNUM60

intmain(void)

{

intn;

intfd[2];

pid_tpid;

charbuf[BUFNUM];

if(pipe(fd)<0)

{

fprintf(stderr,"CreatPipeError:

%s\n",strerror(errno));

exit(EXIT_FAILURE);

}

if((pid=fork())<0)

{

fprintf(stderr,"ForkError:

%s\n",strerror(errno));

exit(EXIT_FAILURE);

}

if(pid>0)/*parent*/

{

close(fd[0]);

write(fd[1],"I'myourfather.\n",17);

write(fd[1],"I'mwaitingforyourtermination.\n",35);

wait(NULL);

}

else/*child*/

{

close(fd[1]);

n=read(fd[0],buf,BUFNUM);

printf("%dbytesread:

%s",n,buf);

}

return0;

}

上述程序父进程创建一个管道,然后再创建一个子进程,由于子进程是父进程的精确拷贝,因此子进程也复制了父进程的文件描述符表信息,从而可以访问到父进程创建的管道。

接着父进程关闭管道的读端,并向管道写入一些信息;而子进程则关闭管道的写端,并从读端获得父进程写入的信息。

请思考:

(1)如果将pipe()调用放在fork()之后进行,那么上述父子进程是否还能进行管道通信?

(2)上述程序运行后,子进程从管道中读到的信息是17个字节,还是52个字节,还是60个字节?

为什么?

(3)如果子进程执行read()时,第3个参数的值为5,那么程序运行的结果会怎样?

管道中未取走的信息在读端未关闭以前会不会消失?

(4)父子进程的执行顺序是否会影响到程序运行的结果?

5.有名管道

管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道(namedpipe或FIFO)提出后,该限制得到了克服。

FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。

这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。

值得注意的是,FIFO严格遵循先进先出(firstinfirstout),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。

它们不支持诸如lseek()等文件定位操作。

有名管道通过mkfifo创建:

#include

#include

intmkfifo(constchar*pathname,mode_tmode)

该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。

第二个参数与打开普通文件的open()函数中的mode参数相同。

有名管道创建成功,mkfifo()返回0;否则返回-1。

如果mkfifo的第一个参数是一个已经存在的路径名时,错误代码中会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。

与普通文件类似,有名管道在使用之前必须先进行open操作,具体类似于文件的打开方式:

#include

#include

#include

intopen(constchar*pathname,intflags);

对有名管道的open操作必须遵循下列规则:

(1)如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。

(2)如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。

一旦打开操作成功,便可通过返回的文件描述符,利用read、write系统调用对管道进行读写操作,读写完成应使用close系统调用关闭有名管道。

(3)有名管道使用实例

/******************************************************

*namedPipeDemo.c*

******************************************************/

#include

#include

#include

#include

#include

#include

#defineFIFO_NAME"/tmp/myfifo"

main()

{

intfd;

charw_buf[50];

intw_num;

//若fifo已存在,则直接使用,否则创建它

if((mkfifo(FIFO_NAME,0777)<0)&&(errno!

=EEXIST))

{

printf("cannotcreatefifo...\n");

exit

(1);

}

//以阻塞型只写方式打开fifo

fd=open(FIFO_NAME,O_WRONLY,0);

if(fd==-1)

if(errno==ENXIO)

{

printf("cannotopenfifoforread...\n");

exit

(1);

}

//通过键盘输入字符串,再将其写入fifo,直到输入"exit"为止

while

(1)

{

printf("pleaseinputsomething:

");

scanf("%s",w_buf);

w_num=write(fd,w_buf,strlen(w_buf));

printf("realwritenumis%d\n",w_num);

if(strcmp(w_buf,"exit")==0)break;

}

}

/******************************************************

*namedPipeDemo2.c*

******************************************************/

#include

#include

#include

#include

#defineFIFO_NAME"/tmp/myfifo"

main()

{

charr_buf[50];

intfd;

intr_num;

//若fifo已存在,则直接使用,否则创建它

if((mkfifo(FIFO_NAME,0777)<0)&&(errno!

=EEXIST))

{

printf("cannotcreatefifo...\n");

exit

(1);

}

//以阻塞型只读方式打开fifo

fd=open(FIFO_NAME,O_RDONLY,0);

if(fd==-1)

{

printf("open%sforreaderror\n");

exit

(1);

}

//通过键盘输入字符串,再将其写入fifo,直到输入"exit"为止

while

(1)

{

memset(r_buf,0,sizeof(r_buf));

r_num=read(fd,r_buf,50);

if(r_num==-1)

if(errno==EAGAIN)

printf("nodataavlaible\n");

printf("%dbytesread:

%s\n",r_num,r_buf);

if(strcmp(r_buf,"exit")==0)break;

sleep

(1);

}

unlink(FIFO_NAME);//删除fifo

}

程序编译和运行结果:

<二>Linux消息队列通信机制

Linux系统中,若干个进程可以共享一个消息队列,系统允许其中的一个或多个进程向消息队列写入消息,同时也允许一个或多个进程从消息队列中读取消息,从而完成进程之间的信息交换,这种通信机制被称作消息队列通信机制。

1.数据结构

(1)消息缓冲区structmsgbuf

消息缓冲区是用来存放消息内容的结构体,而且这个结构体的第一个成员必须是一个大于0的长整数,表示对应消息的类型;不过,系统对结构体中其余成员的类型不做任何限制。

include/linux/msg.h中给出的消息缓冲格式如下:

 structmsgbuf{/*消息定义的参照格式*/

    longmtype;/*消息类型(大于0的长整数)*/

       charmtext[1];/*消息正文*/

};

应用程序员可以重新定义消息缓冲区结构体,其中,成员(mtext)不仅能定义为长度为1的字符数组,也可以定义成长度大于1的字符数组,或定义成其他的数据类型,Linux也允许消息正文的长度为0,即结构体中没有mtext域。

虽然,Linux没限定mtext的类型,但却限定了消息的长度,一个消息的最大长度由宏MSGMAX决定,根据版本的不同,其取值可能为8192或其他值。

(2)消息结构structmsg

消息队列中的每个消息节点中不仅包含了消息内容,还包含了一些其他信息,消息节点由消息结构来描述。

include/linux/msg.h中给出的消息结构格式如下:

structmsg{

   structmsg*msg_next;  /*消息队列链接指针,指向队列中的下一条消息*/

   longmsg_type;         /*消息类型,同structmsgbuf中的mtype*/

   char*msg_spot;        /*消息正文的地址,指向msgbuf的消息正文*/

time_tmsg_stime;      /*消息发送的时间*/

   shortmsg_ts;          /*消息正文的大小*/

};

(3)IPC对象访问权限structipc_perm

structipc_perm{

key_tkey;/*IPC对象键值*/

ushortuid;/*ownereuidandegid*/

ushortgid;

ushortcuid;/*creatoreuidandegid*/

ushortcgid;

ushortmode;/*访问权限*/

ushortseq;/*slotusagesequencenumber,即IPC对象使用频率信息*/

};

其中:

key是IPC对象(例如消息队列,共享存储器等)的键值,每个IPC对象都关联着一个唯一的长整型的键值,不同的进程通过相同的键值可访问到同一个IPC对象。

用户进程在创建IPC对象时可以指定key为某个大于0的整数,此时,需要用户自己保证该key值不与系统中存在的其他IPC键值相冲突。

更常用的方式是通过函数调用ftok(pathname,proj_jd)请求系统为用户进程生成一个键值,其中的pathname是一个实际存在的文件的路径名,而且用户进程具有对该文件的访问权限,proj_jd是一个整数,但ftok只会用到其低8位的值(该值不能为0),只要路径名访问到的是同一个文件,而且proj_jd的低8位的值相同,则ftok()调用便将产生相同的键值;如果使用不同的文件路径名和proj_jd,虽然系统不能保证、但通常生成的键值是不同的。

mode中给出了该IPC对象的访问权限,它可以是下列权限的组合:

访问权限八进制整数

拥有者可读0400

拥有者可写0200

同组用户可读0040

同组用户可写0020

其他用户可读0004

用户用户可写0002

(3)消息队列结构体structmsqid_ds

系统中每个消息队列由一个structmsqid_ds类型的变量来描述,structmsqid_ds的格式如下:

structmsqid_ds{

   structipc_permmsg_perm;/*消息队列访问权限*/

   structmsg*msg_first;   /*队列上第一条消息,即链表头*/

   structmsg*msg_last;   /*队列中的最后一条消息,即链表尾*/

   time_tmsg_stime;       /*发送给队列的最后一条消息的时间*/

   time_tmsg_rtime;        /*从消息队列接收到最后一条消息的时间*/

   time_tmsg_ctime;            /*最后修改队列的时间*/

   ushortmsg_cbytes;         /*队列上所有消息总的字节数*/

   ushortmsg_qnum;         /*当前队列上消息的个数*/

   ushortmsg_qbytes;       /*队列允许的最大的字节数*/

   ushortmsg_lspid;          /*发送最后一条消息的进程的pid*/

   ushortmsg_lrpid;          /*接收最后一条消息的进程的pid*/

};

Linux还通过宏MSGMNB限定了一个消息队列的最大长度(队列中所有消息总的字节数)。

2.消息队列相关的系统调用

Linux提供了一组消息队列相关的系统调用来方便用户进行消息通信。

(1)msgget系统调用

①函数原型:

#include

#include

#include

intmsgget(key_tkey,intmsgflg);

②参数:

keykey为0(IPC_PRIVATE),则创建一个新的消息队列;否则,key为一个大于0的长整数,它对应于消息队列的键值,通常是通过ftok()函数生成的。

msgflg对消息队列的访问权限和控制命令的组合。

其中访问权限见“IPC对象访问权限structipc_perm”部分的说明。

而控制命令IPC_CREAT表示,如果key对应的消息队列不存在,则创建它;而IPC_EXCL必须与IPC_CREATT一起使用,它表示:

如果key对应的消息队列不存在,则创建一个新的队列,否则返回-1。

③功能:

如果IPC_CREAT单独使用,semget()为一个新创建的消息队列返回标识数,或者返回具有相同键值的已存在消息队列标识数。

如果IPC_EXCL与IPC_CREAT一起使用,要么创建一个新的队列并返回它的标识数,如果队列已存在,则返回-1。

④返回值:

成功,返回消息队列的标识数;出错,返回-1,同时将错误代码存放在error中。

对于新创建的消息队列,其msqid_ds结构成员变量的初值设置如下:

   msg_qnum、msg_lspid、msg_lrpid设置为0;

msg_stime、msg_rtime设置为0;

    msg_ctime设置为当前时间;

msg_qbytes设成系统的限制值,即宏MSGMNB;

msgflg的读写权限写入msg_perm.mode中;

msg_perm结构的uid和cuid成员被设置成当前进程的有效用ID,id和cuid成员被设置成当前进程的有效组ID。

⑤错误代码:

EACCES:

指定的消息队列已存在,但调用进程没有权限访问它

EEXIST:

key指定的消息队列已存在,而msgflg中同时指定IPC_CREAT和IPC_EXCL标志

ENOENT:

key指定的消息队列不存在同时msgflg中没有指定IPC_CREAT标志

ENOMEM:

需要建立消息队列,但内存不足

ENOSPC:

需要建立消息队列,但已达到系统的限制

(2)msgsnd系统调用

①函数原型:

#include

#include

#include

intmsgsnd(intmsqid,structmsgbuf*msgp,size_tmsgsz,intmsgflg);

②参数:

msqid消息队列的标识数

msgp存放欲发送消息内容的消息缓冲区指针

msgsz消息正文(而非整个消息结构)的长度

msgflg0——消息队列满时,msgsnd将会阻塞

IPC_NOWAIT——消息队列满时,msgsnd立即返回-1

MSG_NOERROR——消息正文长度超过msgsz字节时,不报错,而是直接截去其中多余的部分,并只将前面的msgsz字节发送出去

③功能:

在标识数为msqid的消息队列中添加一个消息,即向标识数为msqid的消息队列发送一个消息。

④返回值:

消息发送成功,返回0;否则返回-1,同时error中存有错误代码

⑤错误代码:

EAGAIN——参数msgflg设为IPC_NOWAIT,而消息队列已满

EACCESS——无权限写入消息队列

EFAULT——参数msgp指向的地址无法访问

EIDRM——标识符为msqid的消息队列已被删除

EINTR——队列已满而处于阻塞的情况下,被信号唤醒

EINVAL——无效的参数msqid、或消息类型type小于等于0、或msgsz为负数或超过系统限制值MSGMAX

ENOMEM——系统无足够内存空间存放msgbuf消息的副本

(3)msgrcv系统调用

①函数原型:

#include

#include

#include

ssize_tmsgrcv(intmsqid,structmsgbuf*msgp,size_tmsgsz,longmsgtyp,intmsgflg);

②参数:

msqid消息队列的标识数

msgp存放欲接收消息内容的消息缓冲区指针

msgsz消息正文(而非

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 经管营销 > 经济市场

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2