实验三 线程控制和进程间通信
一、实验目的
通过Linux管道通信机制、消息队列通信机制的使用,加深对不同类型的进程通信方式的理解。
二、实验内容:
1. 熟悉Linux的管道通信机制 2. 熟悉Linux的消息队列通信机制
三、思考
1. 有名管道和无名管道之间有什么不同? 2. 管道的读写与文件的读写有什么异同?
3. Linux消息队列通信机制中与教材中的消息缓冲队列通信机制存在哪些异同?
四、实验指导
<一>Linux管道通信机制
管道是所有UNIX都提供的一种进程间通信机制,它是进程之间的一个单向数据流,一个进程可向管道写入数据,另一个进程则可以从管道中读取数据,从而达到进程通信的目的。
1.无名管道 无名管道通过pipe()系统调用创建,它具有如下特点:
(1) 它只能用于具有亲缘关系的进程(如父子进程或者兄弟进程)之间的通信。
(2) 管道是半双工的,具有固定的读端和写端。虽然pipe()系统调用返回了两个文件描述符,但每个进程在使用一个文件描述符之前仍需先将另一个文件描述符关闭。如果需要双向的数据流,则必须通过两次pipe()建立起两个管道。
(3) 管道可以看成是一种特殊的文件,对管道的读写与文件的读写一样使用普通的read、write等函数,但它不是普通的文件,也不属于任何文件系统,而只存在于内存中。
2.pipe系统调用 (1)函数原型
#include
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=0,读进程阻塞等待数据被写入管道;
② 0
(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
int n;
int fd[2]; pid_t pid;
char buf[BUFNUM]; if (pipe(fd) < 0) {
fprintf(stderr,\:%s \\n\ exit(EXIT_FAILURE); }
if ((pid = fork()) < 0) {
fprintf(stderr,\:%s \\n\ exit(EXIT_FAILURE); }
if (pid > 0) /* parent */ {
close(fd[0]);
write(fd[1], \
write(fd[1], \ wait(NULL); }
else /* child */ {
close(fd[1]);
n = read(fd[0], buf, BUFNUM);
printf(\ }
return 0; }
上述程序父进程创建一个管道,然后再创建一个子进程,由于子进程是父进程的精确拷贝,因此子进程也复制了父进程的文件描述符表信息,从而可以访问到父进程创建的管道。接着父进程关闭管道的读端,并向管道写入一些信息;而子进程则关闭管道的写端,并从读端获得父进程写入的信息。
请思考:
(1)如果将pipe()调用放在fork()之后进行,那么上述父子进程是否还能进行管道通信?
(2)上述程序运行后,子进程从管道中读到的信息是17个字节,还是52个字节,还是60个字节?为什么?
(3)如果子进程执行read()时,第3个参数的值为5,那么程序运行的结果会怎样?管道中未取走的信息在读端未关闭以前会不会消失?
(4)父子进程的执行顺序是否会影响到程序运行的结果? 5.有名管道
管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道(named pipe或FIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
有名管道通过mkfifo创建: #include
int mkfifo(const char * pathname, mode_t mode) 该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode 参数相同。 有名管道创建成功,mkfifo()返回0;否则返回-1。如果mkfifo的第一个参数是一个已经存在的路径名时,错误代码中会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。
与普通文件类似,有名管道在使用之前必须先进行open操作,具体类似于文件的打开方式:
#include
int open(const char *pathname, int flags); 对有名管道的open操作必须遵循下列规则:(1)如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当
前打开操作没有设置阻塞标志)。(2)如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。
一旦打开操作成功,便可通过返回的文件描述符,利用read、write系统调用对管道进行读写操作,读写完成应使用close系统调用关闭有名管道。
(3)有名管道使用实例
/****************************************************** * namedPipeDemo.c * ******************************************************/ #include
#define FIFO_NAME \main() {
int fd;
char w_buf[50]; int w_num;
// 若fifo已存在,则直接使用,否则创建它 if((mkfifo(FIFO_NAME,0777)<0)&&(errno!=EEXIST)) {
printf(\ exit(1); }
//以阻塞型只写方式打开fifo
fd=open(FIFO_NAME,O_WRONLY,0); if(fd==-1)
if(errno==ENXIO) {
printf(\ exit(1); }
// 通过键盘输入字符串,再将其写入fifo,直到输入\为止 while(1) {
printf(\ scanf(\