实验三_进程间通信 (1)

实验三_进程间通信 (1)
实验三_进程间通信 (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

int pipe(int filedes[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

#define BUFNUM 60

int main(void)

{

int n;

int fd[2];

pid_t pid;

char buf[BUFNUM];

if (pipe(fd) < 0)

{

fprintf(stderr,"Creat Pipe Error:%s \n",strerror(errno));

exit(EXIT_FAILURE);

}

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

{

fprintf(stderr,"Fork Error:%s \n",strerror(errno));

exit(EXIT_FAILURE);

}

if (pid > 0) /* parent */

{

close(fd[0]);

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

write(fd[1], "I'm waiting for your termination.\n", 35);

wait(NULL);

}

else /* child */

{

close(fd[1]);

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

printf("%d bytes read:%s",n,buf);

}

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

#include

int mkfifo(const char * pathname, mode_t mode)

该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode 参数相同。有名管道创建成功,mkfifo()返回0;否则返回-1。如果mkfifo的第一个参数是一个已经存在的路径名时,错误代码中会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。

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

#include

#include

#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

#include

#include

#include

#include

#include

#define FIFO_NAME "/tmp/myfifo"

main()

{

int fd;

char w_buf[50];

int w_num;

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

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

{

printf("cannot create fifo...\n");

exit(1);

}

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

fd=open(FIFO_NAME,O_WRONLY,0);

if(fd==-1)

if(errno==ENXIO)

{

printf("cannot open fifo for read...\n");

exit(1);

}

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

while(1)

{

printf("please input something:");

scanf("%s",w_buf);

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

printf("real write num is %d\n",w_num);

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

}

}

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

* namedPipeDemo2.c *

******************************************************/ #include

#include

#include

#include

#define FIFO_NAME "/tmp/myfifo"

main()

{

char r_buf[50];

int fd;

int r_num;

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

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

{

printf("cannot create fifo...\n");

exit(1);

}

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

fd=open(FIFO_NAME,O_RDONLY,0);

if(fd==-1)

{

printf("open %s for read error\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("no data avlaible\n");

printf(" %d bytes read:%s\n",r_num,r_buf);

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

sleep(1);

}

unlink(FIFO_NAME);//删除fifo

}

程序编译和运行结果:

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

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

1.数据结构

(1)消息缓冲区struct msgbuf

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

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

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

char mtext[1]; /*消息正文*/

};

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

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

(2)消息结构struct msg

消息队列中的每个消息节点中不仅包含了消息内容,还包含了一些其他信息,消息节点由消息结构来描述。include/linux/msg.h中给出的消息结构格式如下:struct msg {

struct msg *msg_next; /*消息队列链接指针,指向队列中的下一条消息 */ long msg_type; /*消息类型,同struct msgbuf中的mtype*/ char *msg_spot; /* 消息正文的地址,指向msgbuf的消息正文 */

time_t msg_stime; /* 消息发送的时间 */

short msg_ts; /* 消息正文的大小 */

};

(3)IPC对象访问权限struct ipc_perm

struct ipc_perm{

key_t key; /* IPC对象键值 */

ushort uid; /* owner euid and egid */

ushort gid;

ushort cuid; /* creator euid and egid */

ushort cgid;

ushort mode; /* 访问权限 */

ushort seq; /* slot usage sequence number,即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)消息队列结构体struct msqid_ds

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

struct msqid_ds {

struct ipc_perm msg_perm; /* 消息队列访问权限*/

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

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

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

time_t msg_rtime; /* 从消息队列接收到最后一条消息的时间 */ time_t msg_ctime; /* 最后修改队列的时间*/

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

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

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

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

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

};

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

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

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

(1)msgget系统调用

①函数原型:

#include

#include

#include

int msgget(key_t key, int msgflg);

②参数:

key key为0(IPC_PRIV ATE),则创建一个新的消息队列;否则,key为

一个大于0的长整数,它对应于消息队列的键值,通常是通过ftok()

函数生成的。

msgflg 对消息队列的访问权限和控制命令的组合。其中访问权限见“IPC

对象访问权限struct ipc_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

int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg);

②参数:

msqid 消息队列的标识数

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

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

msgflg 0 ——消息队列满时,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_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long

msgtyp, int msgflg);

②参数:

msqid 消息队列的标识数

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

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

msg-typ 0 ——接收消息队列中的第一个消息

>0 ——接收第一个类型为msgtyp的消息

<0 ——接收第一个类型小于等于msgtyp的绝对值的消息

msgflg 0 ——没有可以接收的消息时,msgrcv阻塞

IPC_NOWAIT ——没有可以接收的消息时,立即返回-1

MSG_EXCEPT ——返回第一个类型不为msgtyp的消息

MSG_NOERROR——消息正文长度超过msgsz字节时,将直接截去其

中多余的部分

③功能:如果传递给参数msgflg的值为IPC_NOWAIT,并且没有可取的消息,

那么给调用进程返回ENOMSG错误码,否则,调用进程阻塞,直到一

条满足要求的消息到达消息队列。如果进程正在等待消息,而相应

的消息队列被删除,则返回EIDRM。如果当进程正在等待消息时,

捕获到了一个信号,则返回EINTR

④返回值:接收成功,返回实际接收到的消息正文的字节数;否则返回-1,同

时error中存有错误代码

⑤错误代码:E2BIG ——消息长度超过msgsz,且MSG_NOERROR标志没被使用

EACCESS——无权限读取消息队列

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

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

EINTR ——等待消息的情况下,被信号唤醒

EINVAL ——无效的参数msqid、或msgsz为负数

ENOMSG ——参数msgflg设为IPC_NOWAIT,但无满足要求的消息

可接收

(4)msgctl系统调用

①函数原型:

#include

#include

#include

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

②参数:

msqid 消息队列的标识数

cmd IPC_STAT ——将对应消息队列结构体的值复制到一份到buf所

指的结构体中,调用者必须有读消息队列的权限 IPC_SET ——将buf所指结构体中的部分信息:

msg_perm.uid,msg_perm.gid,mst_perm.mode,m

sg_qbytes写到消息队列结构体中,并且更新消息

队列结构体msg_ctime成员的值。调用者必须有相应

的权限。

IPC_RMD ——删除消息队列,并唤醒该消息队列上等待读或等待写

的进程。调用者必须有相应的权限。

③功能:获取或设置消息队列的属性信息,或者删除消息队列

④返回值:成功,返回0;否则返回-1,同时error中存有错误代码

⑤错误代码:EACCESS—— cmd为IPC_STAT,但调用进程无读消息队列的权限

EFAULT ——参数cmd为IPC_STAT或IPC_SET ,但buf指向的

地址无法访问

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

EINTR ——等待消息的情况下,被信号唤醒

EINVAL ——cmd或msqid为无效的参数

EPERM ——参数cmd为IPC_RMD或IPC_SET ,但调用进程无足

够的权限

3.实例

//sender.c

#include

#include

#include

#include

#include

#include

#include

#include

#define MSG_FILE "sender.c"

#define BUFFER 255

#define PERM S_IRUSR|S_IWUSR

struct msgbuf

{

long mtype;

char mtext[BUFFER+1];

};

char *message[3]= {"I'm Sender,there are some messages for you.",\

"This is 1st message.",\

"This is 2nd message."

};

int main()

{

struct msgbuf msg;

key_t key;

int msgid;

int i;

if((key=ftok(MSG_FILE,66))==-1)

{

fprintf(stderr,"Creat Key Error:%s \n",strerror(errno));

exit(EXIT_FAILURE);

}

if((msgid=msgget(key,PERM|IPC_CREAT))==-1)

{

fprintf(stderr,"Creat MessageQueue Error:%s \n",strerror(errno)); exit(EXIT_FAILURE);

}

msg.mtype=1;

for(i=0; i<3; i++)

{

strncpy(msg.mtext,message[i],BUFFER);

msgsnd(msgid,&msg,sizeof(struct msgbuf),0);

}

memset(&msg,'\0',sizeof(struct msgbuf));

msgrcv(msgid,&msg,sizeof(struct msgbuf),2,0);

printf("Sender receive: %s\n",msg.mtext);

if(msgctl(msgid, IPC_RMID, 0) == -1)

{

fprintf(stderr, "Remove MessageQueue Error%s \n",strerror(errno)); exit(EXIT_FAILURE);

}

exit(EXIT_SUCCESS);

}

//receiver.c

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define MSG_FILE "sender.c"

#define BUFFER 255

#define PERM S_IRUSR|S_IWUSR

struct msgbuf

{

long mtype;

char mtext[BUFFER+1];

};

int main()

{

struct msgbuf msg;

key_t key;

int msgid;

int i;

char *myask="I'm receiver, 3 messages received from you.";

if((key=ftok(MSG_FILE,66))==-1)

{

fprintf(stderr,"Creat Key Error:%s \n",strerror(errno));

exit(EXIT_FAILURE);

}

if((msgid=msgget(key,PERM|IPC_CREAT))==-1)

{

fprintf(stderr,"Creat MessageQueue Error:%s \n",strerror(errno));

exit(EXIT_FAILURE);

}

for(i=0; i<3; i++)

{

msgrcv(msgid,&msg,sizeof(struct msgbuf),1,0);

printf("Receiver receive: %s\n",msg.mtext);

}

msg.mtype=2;

strncpy(msg.mtext,myask,BUFFER);

msgsnd(msgid,&msg,sizeof(struct msgbuf),0);

exit(EXIT_SUCCESS);

}

程序的运行结果如下:

上述程序运行时,第三行命令名后的添加的‘&’,可以使命令“./receiver”在后台运行,shell会立即显示命令提示符,等待用户输入下一条命令,再输入“./sender”便可让上述两个程序同时运行。请思考:

(1)如果不使用后台进程,那应如何运行上述程序,达到进程通信的目的?

(2)发送给sender的消息、和发送给receiver的消息是存放在相同的消息队列中,还是存放在各自的消息队列中?

(3)Sender、receiver获取/创建消息队列时使用的键值是否必须相同,如果键值不同会发生什么情况?

(4)程序中函数调用msgctl(msgid, IPC_RMID, 0)的目的是什么?如果没有执行该函数调用,那程序多次运行会不会创建多个消息队列?

<四>编程实现下述功能

1.由父进程创建一个管道,然后再创建2个子进程,并由这两个兄弟进程利用管道进行进程通信:子进程1使用管道的写端,子进程2使用管道的读端。通信的具体内容可根据自己的需要随意设计,要求能试验阻塞型读写过程中的各种情况。运行程序,观察各种情况下,进程实际读写的字节数以及进程阻塞唤醒的情况。

2.编写程序sender,它创建一个消息队列;然后,循环等待用户通过终端输入一串字符,将这串字符通过消息队列发送给receiver,直到用户输入“exit”为止;最后,它向receiver 进程发送消息“end”,并且等待receiver的应答,等到应答消息后,将接收到的应答信息显示在终端屏幕上,删除消息队列,结束程序的运行。编写receiver程序,它通过消息队列接收来自sender的消息,将消息显示在终端屏幕上,直至收到内容为“end”的消息为止,此时,它向sender发送一个应答消息“over”,结束程序的运行。

<五> ipcs和ipcrm命令

通过man命令查阅ipcs和ipcrm命令的格式和使用方式,查看目前系统中是否存在消息队列,如果没有,想办法创建若干消息队列,再次使用ipcs查看它们的情况,最后,请将所有的消息队列全部删除。

实验六:进程间通信 实验目的: 学会进程间通信方式:无名管道,有名管道,信号,消息队列, 实验要求: (一)在父进程中创建一无名管道,并创建子进程来读该管道,父进程来写该管道(二)在进程中为SIGBUS注册处理函数,并向该进程发送SIGBUS信号(三)创建一消息队列,实现向队列中存放数据和读取数据 实验器材: 软件:安装了Linux的vmware虚拟机 硬件:PC机一台 实验步骤: (一)无名管道的使用 1、编写实验代码pipe_rw.c #include #include #include #include #include #include int main() { int pipe_fd[2];//管道返回读写文件描述符 pid_t pid; char buf_r[100]; char* p_wbuf; int r_num; memset(buf_r,0,sizeof(buf_r));//将buf_r初始化 char str1[]=”parent write1 “holle””; char str2[]=”parent write2 “pipe”\n”; r_num=30; /*创建管道*/ if(pipe(pipe_fd)<0) { printf("pipe create error\n"); return -1; } /*创建子进程*/ if((pid=fork())==0) //子进程执行代码 {

//1、子进程先关闭了管道的写端 close(pipe_fd[1]); //2、让父进程先运行,这样父进程先写子进程才有内容读sleep(2); //3、读取管道的读端,并输出数据 if(read(pipe_fd[0],buf_r, r_num)<0) { printf(“read error!”); exit(-1); } printf(“%s\n”,buf_r); //4、关闭管道的读端,并退出 close(pipe_fd[1]); } else if(pid>0) //父进程执行代码 { //1、父进程先关闭了管道的读端 close(pipe_fd[0]); //2、向管道写入字符串数据 p_wbuf=&str1; write(pipe_fd[1],p_wbuf,sizof(p_wbuf)); p_wbuf=&str2; write(pipe_fd[1],p_wbuf,sizof(p_wbuf)); //3、关闭写端,并等待子进程结束后退出 close(pipe_fd[1]); } return 0; } /*********************** #include #include #include #include #include #include int main() { int pipe_fd[2];//管道返回读写文件描述符 pid_t pid; char buf_r[100]; char* p_wbuf; int r_num;

操作系统实验报告 实验三、进程通信(一) ——管道及共享内存 一、实验目的 熟悉和掌握LINUX系统的管道通信和共享内存通信。 二、实验内容 (一)、管道通信 1、实验原理: 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统。 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。 利用系统调用pipe()可创建一个简单的管道。 int fd[2]; pipe(fd); 调用成功,fd[0]存放供读进程使用的文件描述符(管道出口),fd[1]存放供写程使用的文件描述符(管道入口)。 一个进程在由pipe()创建管道后,一般再fork()一个子进程,然后通过管道实现父子进程间的通信,子进程将从父进程那里继承所有打开的文件描述符。则父子两个进程都能访问组成管道的两个文件描述符,这样子进程可以向父进程发送消息(或者相反)。 发送进程利用文件系统的系统调用write(fd[1],buf,size),把buf中size个字符送入fd[1],接收进程利用read(fd[0],buf,size),把从fd[0]读出的size个字符置入buf中。这样管道按FIFO(先进先出)方式传送信息。 2、实验内容: #include main() { int x,fd[2]; char buf[30],s[30]; pipe(fd); while ((x=fork())==-1);

if (x==0) { close(fd[0]); printf("Child Process!\n"); strcpy(buf,"This is an example\n"); write(fd[1],buf,30); exit(0); } else{ close(fd[1]); printf("Parent Process!\n"); read(fd[0],s,30); printf("%s\n",s); printf("x value in Parent Process:%d!\n",x); } } (1)阅读上述父子进程利用管道进行通信的例子,写出程序的运行结果并分析。 程序运行x=fork()后,创建里一个子进程,在子进程里x的返回值为0,在父进程里x的返回值为7684,则子进程会执行if语句里的代码段,先关闭了管道的读入端,再在屏幕上输出“Child Process!”,然后将“This is an example\n”存入buf数组中,通过调用write()函数将buf数组的内容从管道的写入端写入,而父进程会执行else语句里的代码段,先关闭了管道的写入端,再在屏幕上输出“Parent Process!”,然后通过调用read ()函数将buf数组的内容从管道的读入端读入冰存储在s数组里,接着通过printf()函数将s数组里的内容输出到屏幕上,最后在屏幕上输出父进程中x的值。 (2)编写程序:父进程利用管道将一字符串交给子进程处理。子进程读字符串,将里面的字符反向后再交给父进程,父进程最后读取并打印反向的字符串。 #include main() { int i,x,fd[2]; char buf[20],s[20],cpy[20],m[20]; strcpy(buf,"This is an example\n"); pipe(fd); write(fd[1],buf,20);

实验三进程通信(二) 实验目的: 1、理解进程通信原理和基本技术 2、了解linux系统进程间通信机构(IPC); 3、理解linux关于共享内存的概念; 4、掌握linux支持进程间共享内存的系统调用; 5、巩固进程同步概念。 实验内容: (一)闹钟。用c语言编程,实现进程间通过信号进行通信。 用fork()创建两个进程,子进程在等待5秒后用系统调用kill()向父进程发送SIGALRM 信号,父进程用系统调用signal()捕捉SIGALRM信号。参考程序如下: #include #include #include static int alarm_fired = 0; //闹钟未设置 //模拟闹钟 void ding(int sig) { alarm_fired = 1; //设置闹钟 } int main() { int pid; printf("alarm application starting\n"); if((pid = fork( )) == 0) { //子进程5秒后发送信号SIGALRM给父进程 sleep(5); kill(getppid(), SIGALRM); //exit(0); return 0; } //父进程安排好捕捉到SIGALRM信号后执行ding函数 printf("waiting for alarm to go off\n"); (void) signal(SIGALRM, ding); pause(); //挂起父进程,直到有一个信号出现 if (alarm_fired) printf("Ding!\n"); printf("done\n");

实验三进程间通信(2学时) 一、实验目的 (1)了解什么是信号。 (2)熟悉LINUX系统中进程之间软中断通信的基本原理。 (3)熟悉LINUX支持的管道通信方式。 二、实验内容 (1)编写一段程序,使其现实进程的软中断通信。 即:使用系统调用fork()创建两个子进程,再用系统调用signal()让父进程捕捉键盘上来的中断信号(即按 ctrl+c 键);当捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后,分别输出下列信息后终止: Child Process11 is killed by Parent! Child Process12 is killed by Parent! 父进程等待两个子进程终止后,输出如下的信息后终止 Parent Process is killed! 要求:运行以下参考程序并分析结果。 <参考程序> #include #include #include #include void waiting(),stop(),alarming(); int wait_mark; main() { int p1,p2; if(p1=fork()) /*创建子进程p1*/ { if(p2=fork()) /*创建子进程p2*/ { //父进程 wait_mark=1; signal(SIGINT,stop); /*接收到^c信号,转stop*/

signal(SIGALRM,alarming);/*接受SIGALRM*/ waiting(); kill(p1,16); /*向p1发软中断信号16*/ kill(p2,17); /*向p2发软中断信号17*/ wait(0); /*同步*/ wait(0); printf("parent process is killed!\n"); exit(0); //会暂时停止目前进程的执行,直到有信号来到或子进程结束。 } else { wait_mark=1; signal(17,stop); signal(SIGINT,SIG_IGN); /*忽略 ^c信号*/ while (wait_mark!=0); lockf(1,1,0); printf("child process2 is killed by parent!\n"); lockf(1,0,0); exit(0); } } else { wait_mark=1; signal(16,stop); signal(SIGINT,SIG_IGN); /*忽略^c信号*/ while (wait_mark!=0); lockf(1,1,0); printf("child process1 is killed by parent!\n"); lockf(1,0,0); exit(0); } } void waiting() { sleep(5); if (wait_mark!=0) kill(getpid(),SIGALRM); } void alarming()

进程间通信实验报告 班级:10网工三班学生姓名:谢昊天学号:1215134046 实验目的和要求: Linux系统的进程通信机构 (IPC) 允许在任意进程间大批量地交换数据。本实验的目的是了解和熟悉Linux支持的消息通讯机制及信息量机制。 实验内容与分析设计: (1)消息的创建,发送和接收。 ①使用系统调用msgget (), msgsnd (), msgrev (), 及msgctl () 编制一长度为1k 的消息的发送和接收程序。 ②观察上面的程序,说明控制消息队列系统调用msgctl () 在此起什么作用? (2)共享存储区的创建、附接和段接。 使用系统调用shmget(),shmat(),sgmdt(),shmctl(),编制一个与上述功能相同的程序。(3)比较上述(1),(2)两种消息通信机制中数据传输的时间。 实验步骤与调试过程: 1.消息的创建,发送和接收: (1)先后通过fork( )两个子进程,SERVER和CLIENT进行通信。 (2)在SERVER端建立一个Key为75的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER 。SERVER每接收到一个消息后显示一句“(server)received”。 (3)CLIENT端使用Key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后的一个消息,既是 SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(client)sent”。 (4)父进程在 SERVER和 CLIENT均退出后结束。 2.共享存储区的创建,附接和断接: (1)先后通过fork( )两个子进程,SERVER和CLIENT进行通信。 (2)SERVER端建立一个KEY为75的共享区,并将第一个字节置为-1。作为数据空的标志.等待其他进程发来的消息.当该字节的值发生变化时,表示收到了该消息,进行处理.然后再次把它的值设为-1.如果遇到的值为0,则视为结束信号,取消该队列,并退出SERVER.SERVER 每接收到一次数据后显示”(server)received”. (3)CLIENT端建立一个为75的共享区,当共享取得第一个字节为-1时, Server端空闲,可发送请求. CLIENT 随即填入9到0.期间等待Server端再次空闲.进行完这些操作后, CLIENT退出. CLIENT每发送一次数据后显示”(client)sent”. (4)父进程在SERVER和CLIENT均退出后结束。 实验结果: 1.消息的创建,发送和接收: 由 Client 发送两条消息,然后Server接收一条消息。此后Client Server交替发送和接收消息。最后一次接收两条消息。Client 和Server 分别发送和接收了10条消息。message 的传送和控制并不保证完全同步,当一个程序不再激活状态的时候,它完全可能继续睡眠,造成上面现象。在多次send message 后才 receive message.这一点有助于理解消息转送的实现机理。

暨南大学本科实验报告专用纸 一、实验目的 学习如何利用管道机制或消息缓冲队列进行进程间的通信,并加深对上述通信机制的理解。提高学生分析问题和解决问题的能力,并学习撰写规范的科学研究报告。 二、实验环境及设备 (一)实验室名称:计算机实验室 (二)主要仪器设备:PC 机、Linux 操作系统环境 三、实验内容 编写一段程序,使用管道来实现父子进程之间的进程通信。子进程项父进程发送自己的进程表示符,以及某字符串。父进程则通过管道读出子进程发来的消息,将消息显示在屏幕上,然后终止。 四、实验调试分析 1、实验函数说明 (1)pipe 头文件 :#include 定义函数: int pipe(int pipedes[2]); 函数说明: pipe()会建立管道,并将文件描述词由参数 pipedes 数组返回。 pipedes[0]为管道里的读取端,所以pipe 用read 调用的 pipedes[1]则为管道的写入端。 返回值: 若成功则返回零,否则返回-1,错误原因存于 errno 中。 错误代码: EMFILE 进程已用完文件描述词最大量 ENFILE 系统已无文件描述词可用。 EFAULT 参数 pipedes 数组地址不合法。 (2)sprintf 函数功能:把格式化的数据写入某个字符串 进程A pipedes[1] pipedes[0] 进程B 写 读

头文件:#include 函数原型:int sprintf( char *buffer, const char *format [, argument] … ); 返回值:字符串长度(strlen) (3)flock 头文件: #include 定义函数: int flock(int fd,int operation); 函数说明: flock()会依参数operation所指定的方式对参数fd所指的文件做各种锁定或解除锁定的动作。此函数只能锁定整个文件,无法锁定文 件的某一区域。 参数 operation有下列四种情况: LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。 LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。 LOCK_UN 解除文件锁定状态。 LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。通常与LOCK_SH 或LOCK_EX 做OR(|)组合。 单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定。 返回值返回0表示成功,若有错误则返回-1,错误代码存于errno。 2、实验调试 实验过程中,由于将字符串"is sending a message to parent!"以字符数组的形式存储的,故只申请了31个字符空间,没有多申请一个字符空间存储末尾的'\0',在程序运行时就会出现错误结果,错误结果如下图所示,理论上应该不会出现错误的,因为在write、read函数中都有限制向管道中写入、读出多少个字符的。但在运行时却出乎意料的出错了,要是申请的空间大于字符个数,就不会出现错误了。在实验过程中还遇到了另一个问题——字符串"is sending a message to parent!"被两次输出。怀疑是该字符串被两次的写入了管道,因为查看程序时只发现一个地方有输出该字符串,该字符串是从管道读出的且只输出一次,该字符串在写入管道时也是只写了一次。总之向管道中写入一次字符串,也读出一次,但是输入该字符串时且输出了两次连续的该字符串,由于实验过程中一直调试该错误程序,没有保留当时的程序,所以没有找到当时错误的原因。

实验三进程管理及进程通信 实验环境: Linux操作系统 实验目的: (1)利用Linux提供的系统调用设计程序,加深对进程概念的理解。 (2)体会系统进程调度的方法和效果。 (3)了解进程之间的通信方式以及各种通信方式的使用。 实验方法: 用vi 编写c 程序(假定程序文件名为prog1.c)编 译程序 $ gcc -o prog1.o prog1.c 或 $ cc -o prog1.o prog1.c 运行 $./prog1.o 实验内容及步骤: 实验1 编写程序。显示进程的有关标识(进程标识、组标识、用户标识等)。经过5 秒钟后,执行另一个程序,最后按用户指示(如:Y/N)结束操作。 编程截图:

运行结果: 实验2 参考例程1,编写程序。实现父进程创建一个子进程。体会子进程与父进程分 别获得不同返回值,进而执行不同的程序段的方法。 例程1:利用fork()创建子进程 /* 用fork()系统调用创建子进程的例子*/ main() { int i; if (fork()) /*父进程执行的程序段*/ i=wait(); /* 等待子进程结束*/{ printf("It is parent process.\n"); printf("The child process,ID number %d, is finished.\n",i); } else{

Printf(“It is child process.\n”); Sleep(10); Exit(); } } 运行结果: 思考: 子进程是如何产生的?又是如何结束的?子进程被创建后它的运行环境是怎样建立的? 答:是由父进程用fock()函数创建形成的,通过exit()函数自我结束,子进程被创建后核心 将其分配一个进程表项和进程标识符,检查同时运行的进程数目,并且拷贝进程表项的数据,由子进程继承父进程所有文件。 实验3 参考例程2,编写程序。父进程通过循环语句创建若干子进程。探讨进程的家族树 以及子进程继承父进程的资源的关系。 例程2:循环调用fork()创建多个子进程。 /*建立进程树*/ #include main() { int i; printf(“My pid is %d, my father’s pid is %d\n”,getpid() ,getppid()); for(i=0; i<3; i++) if(fork()==0) printf(“%d pid=%d ppid=%d\n”, i,getpid(),getppid()); else { j=wait(0); Printf(“%d:The chile %d is finished.\n”,getpid(),j);

实验三进程通信 一.实验学时与类型 学时:2,课外学时:自定 实验类型:设计性实验 二.实验目的 了解Linux的软中断、管道、消息队列、共享存储区等进程间通信方式。 三.实验容 1. 软中断通信机制 (1) 请编写一个程序:循环输出“how are you?”,在按下Ctrl+C后中断显示,输出“Byebye!”后退出程序。 #include #include int k=1; void int_func(int sig) //软中断处理函数 { k=0; } Int main() { signal(SIGINT,int_func);//预置软中断信号处理函数 While(k==1) Printf(“how are you?\n”); Printf(“byebye!”); } (2)使用信号机制实现父子进程同步,父进程先输出A,然后子进程输出B。 #include #include int k=1; void func(int sig) { k=0; } main() { int pid;

pid=fork(); if(pid>0) { printf(“A\n”); kill(pid,12); } else if(pid==0) { signal(12,func); while(k==1) sleep(1); printf(“B\n”); } } 2. 管道机制 (1) 父子进程通过管道传送一串字符。要求:子进程随机从键盘输入一串字符,通过管道发给父进程,父进程从管道中将消息读出并显示出来。 #include #include main() { int pid, fd[2] ; char outpipe[50], inpipe[50]; pipe(fd); pid=fork(); if (pid==0) { Printf(“please input some message:\n”); Fgets(inpipe,sizeof(inpipe),stdin); write(fd[1],inpipe,50); } else if (pid>0); { wait(0); Printf(“father get this message:\n”); read(fd[0],outpipe,50); printf(“%s\n”,outpipe); } }

实验三进程间通信班级: xxx 学号: xxx 姓名: xxx 分数: 1、实验目的: Linux系统的进程通信机构(IPC)允许在任意进程间大批量的交换数据。本实验的目的是了解和熟悉Linux支持的通信机制、共享存储区机制及信号量机制。 2、实验预备内容: 阅读Linux系统的等源码文件,熟悉Linux的三种通信机制。 3、实验内容: (1)消息的创建,发送和接收 (2)使用系统调用msgget(),msgsnd(),msgrev()及msgctl()编制一长度为1k的消息发送和接收程序。 <程序设计> (1)为了便于操作和观察结果,用一个程序作为“引子”,先后fork()两个子进程,SERVER和CLIENT,进行通信。 (2)SERVER端建立一个Key为75的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER。SERVER 每接收到一个消息后显示一句“(server)received”。 (3)CLIENT端使用Key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后一个消息,即是SERVER端需要的结束信号。CLIENT每发送一条 信息后显示一句“(client)sent”。 (4)父进程在SERVER和CLIENT均退出后结束。 4、源代码及运行截图: #include <> #include #include #include #define MSGKEY 75 struct msgform { long mtype; char mtext[1030]; }msg; int msgqid,i; void CLIENT() { int i; msgqid=msgget(MSGKEY,0777); for (i=10;i>=1;i--) { =i;

实验三进程的管道通信 一、实验目的: (1)加深对进程概念的理解,明确进程和程序的区别; (2)学习进程创建的过程,进一步认识并发执行的实质; (3)分析进程争用资源的现象,学习解决进程互斥的方法; (4)学习解决进程同步的方法; (5)掌握Linux系统进程间通过管道通信的具体实现方法。 二、实验内容及要求: (1)使用系统调用pipe()建立一条管道线,两个子进程分别向管道写一句话(写的内容自己定,但要有该进程的一些信息); (2)父进程从管道中读出来自两个子进程的消息,显示在屏幕上;(3)要求:父进程首先接收子进程p1发来的消息,然后再接收子进程p2发来的消息; (4)两个子进程要并发执行; (5)实现管道的互斥使用。当一个子进程正在对管道进行写操作时,另一个欲写入管道的子进程必须等待。使用系统调用lockf(fd[1],1,0)实现对管道的加锁操作,用lockf(fd[1],0,0)解除对管道的锁定; (6)实现父子进程的同步,当父进程试图从一空管道中读取数据时,便进入等待状态,直到子进程将数据写入管道返回后,才将其唤醒。 三、实现: 相关的系统调用

fork() 用于创一个子进程。 格式:int fork(); 返回值:在子进程中返回0;在父进程中返回所创建的子进程的ID值;当返回-1时,创建失败。 wait() 常用来控制父进程与子进程的同步。 在父进程中调用wait(),则父进程被阻塞,进入等待队列,等待子进程结束。当子进程结束时,父进程从wait()返回继续执行原来的程序。 返回值:大于0时,为子进程的ID值;等于-1时,调用失败。 exit() 是进程结束时最常调用的。 格式:void exit( int status); 其中,status为进程结束状态。 pipe() 用于创建一个管道 格式:pipe(int fd); 其中fd是一个由两个数组元素fd[0]和fd[1]组成的整型 数组,fd[0]是管道的读端口,用于从管道读出数据,fd[1]是管道的写端口,用于向管道写入数据。 返回值:0 调用成功;-1 调用失败。 sleep() 使调用进程睡眠若干时间,之后唤醒。 格式:sleep(int t); 其中t为睡眠时间。

操作系统实验三报告 实验题目: 进程管理及进程通信 实验环境: 虚拟机Linux操作系统 实验目的: 1.利用Linux提供的系统调用设计程序,加深对进程概念的理解。 2.体会系统进程调度的方法和效果。 3.了解进程之间的通信方式以及各种通信方式的使用。

实验内容: 例程1: 利用fork()创建子进程 #include<> #include<> #include<> main() { int i; if (fork()) i=wait(0); /*父进程执行的程序段*/ /* 等待子进程结束*/ printf("It is parent process.\n"); printf("The child process,ID number %d, is finished.\n",i); } else{ printf("It is child process.\n"); sleep(10); /*子进程执行的程序段*/ exit(1); /*向父进程发出结束信号*/ } } 运行结果: 思考:子进程是如何产生的又是如何结束的子进程被创建后它的运行环境是怎样建立的

答:子进程是通过函数fork()创建的,通过exit()函数自我结束的,子进程被创建后核心将为其分配一个进程表项和进程标识符,检查同时运行的进程数目,并且拷贝进程表项的数据,由子进程继承父进程的所有文件。 例程2: 循环调用fork()创建多个子进程 #include<> #include<> #include<> main() { int i,j; printf(“My pid is %d, my father’s p id is %d\n”,getpid() ,getppid()); for(i=0; i<3; i++) if(fork()==0) printf(“%d pid=%d ppid=%d\n”, i,getpid(),getppid()); else { j=wait(0); Printf(“ %d:The chile %d is finished.\n” ,getpid(),j); } } 运行结果:

湖北工业大学工程技术学院实验报告 课程名称:操作系统实验内容:实验五进程间通信(二) 学院:工程技术学院专业班级:11gb软件2班 教师:贺红艳成绩: 一、实验目的 1、掌握linux系统中进程通信的基本原理。 2、学会使用linux系统中关于进程通信的一些系统调用。 3、掌握信号与共享存储区的使用方法。 二、相关知识背景: (一)信号 1、信号的基本概念 每个信号都对应一个正整数常量(称为signal number,即信号编号。定义在系统头文件中),代表同一用户的诸进程之间传送事先约定的信息的类型,用于通知某进程发生了某异常事件。每个进程在运行时,都要通过信号机制来检查是否有信号到达。若有,便中断正在执行的程序,转向与该信号相对应的处理程序,以完成对该事件的处理;处理结束后再返回到原来的断点继续执行。实质上,信号机制是对中断机制的一种模拟,故在早期的UNIX版本中又把它称为软中断。 ⑴信号与中断的相似点: ①采用了相同的异步通信方式; ②当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序; ③都在处理完毕后返回到原来的断点; ④对信号或中断都可进行屏蔽。 ⑵信号与中断的区别: ①中断有优先级,而信号没有优先级,所有的信号都是平等的; ②信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行; ③中断响应是及时的,而信号响应通常都有较大的时间延迟。 ⑶信号机制具有以下三方面的功能: ①发送信号。发送信号的程序用系统调用kill( )实现; ②预置对信号的处理方式。接收信号的程序用signal( )来实现对处理方式的预置; ③收受信号的进程按事先的规定完成对相应事件的处理。 2、信号的发送 信号的发送,是指由发送进程把信号送到指定进程的信号域的某一位上。如果目标进程正在一个可被中断的优先级上睡眠,核心便将它唤醒,发送进程就此结束。一个进程可能在其信号域中有多个位被置位,代表有多种类型的信号到达,但对于一类信号,进程却只能记住其中的某一个。进程用kill( )向一个进程或一组进程发送一个信号。 3.对信号的处理 当一个进程要进入或退出一个低优先级睡眠状态时,或一个进程即将从核心态返回用户态时,核心都要检查该进程是否已收到软中断。当进程处于核心态时,即使收到软中断

实验三进程间通信 UNIX/LINUX系统的进程间通信机构(IPC)允许在任意进程间大批量地交换数据。本实验的目的是了解和熟悉LINUX支持的信号量机制、管道机制、消息通信机制及共享存储区机制。 (一)信号量机制实验 【实验目的】 1、了解什么是信号。 2、熟悉LINUX系统中进程之间软中断通信的基本原理。 【实验内容】 本次实验共3部分,前两部分必做。 1、编写一段程序,使用系统调用fork( )创建两个子进程,再用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按ctrl+c键),当捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后,分别输出下列信息后终止:Child process 1 is killed by parent! Child process 2 is killed by parent! 父进程等待两个子进程终止后,输出以下信息后终止: Parent process is killed! <参考程序> # include # include # include main() { int p1, p2; signal(SIGINT,stop);//signal()初始位置 while((p1=fork())==-1); if(p1>0) {① while((p2=fork())= =-1); If(p2>0) { ② wait_mark=1; waiting(0); kill(p1,10); kill(p2,12); wait(); wait);

实验四进程的管道通信实验 【实验目的】 1、了解什么是管道 2、熟悉UNIX/LINUX支持的管道通信方式 【实验内容】 1、编制一段程序,实现进程的管道通信。使用pipe()建立一条管道线。两个子进程p1和p2分别向管道各写一句话: Child 1 is sending message! Child 2 is sending message! 而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。 <参考程序> # include # include # include int pid1,pid2; main() { int fd[2]; char OutPipe[100],InPipe[100]; pipe(fd); while((pid1=fork())= = -1); if(pid1= =0) { lockf(fd[1],1,0); sprintf(OutPipe,” child 1 process is sending message!”); write(fd[1],OutPipe,50); sleep(5); lockf(fd[1],0,0); exit(0); } else { while((pid2=fork())= = -1); if(pid2= =0) { lockf(fd[1],1,0); sprintf(OutPipe,” child 2 process is sending message!”); write(fd[1],OutPipe,50); sleep(5);

lockf(fd[1],0,0); exit(0); } else { wait(0); read(fd[0],InPipe,50); printf(“%s\n”,InPipe); wait(0); read(fd[0],InPipe,50); printf(“%s\n”,InPipe); exit(0); } } } 实验要求:运行程序并分析结果。 2.在父进程中用pipe()建立一条管道线,往管道里写一句话,两个子进程接收这句话。【实验报告】 1、列出调试通过程序的清单,分析运行结果。 2、给出必要的程序设计思路和方法(或列出流程图)。 3、总结上机调试过程中所遇到的问题和解决方法及感想。 【实验相关资料】 一、什么是管道 UNIX系统在OS的发展上,最重要的贡献之一便是该系统首创了管道(pipe)。这也是UNIX系统的一大特色。 所谓管道,是指能够连接一个写进程和一个读进程的、并允许它们以生产者—消费者方式进行通信的一个共享文件,又称为pipe文件。由写进程从管道的写入端(句柄1)将数据写入管道,而读进程则从管道的读出端(句柄0)读出数据。 句柄 读出端 句柄 二、管道的类型: 1、有名管道 一个可以在文件系统中长期存在的、具有路径名的文件。用系统调用mknod( )建立。它克服无名管道使用上的局限性,可让更多的进程也能利用管道进行通信。因而其它进程可以

实验三2_Linux进程间通信 实验三 Linux进程间通信 一、实验目的 熟悉Linux下进程间通信机制,能够使用系统提供的各种通信机制实现并发进程间的数据交换。 二、实验题目 分别使用Linux下的共享存储区、消息、管道等通信机制,编程实现并发进程之间的相互通信。 三、背景材料 (一)需要用到的系统调用 实验可能需要用到的主要系统调用和库函数在下面列出,详细的使用方法说明通过“man 2 系统调用名”或者“man 3 函数名”命令获取。 fork() 创建一个子进程,通过返回值区分是在父进程还是子进程中执行; wait() 等待子进程执行完成; getpid() 获取当前进程id; shmget() 建立一个共享存储区; shmctl() 操纵一个共享存储区; shmat() 把一个共享存储区附接到进程内存空间; shmdt() 把一个已经附接的共享存储区从进程内存空间断开; msgget() 建立一个消息队列; msgctl() 操纵一个消息队列; msgsnd() 发送消息; msgrcv() 接收消息;

signal() 设置对信号的处理方式或处理过程; pipe() 打开管道; lockf() 锁定一个文件。 (二)使用共享存储区的示例程序 下面程序主要用来演示共享存储区的使用方法:首先要使用shmget得到共享存储区句柄(可以新建或连接已有的共享存储区,以关键字标识),然后使用shmat挂接到进程的存储空间(这样才能够访问),当使用完后,使用shmctl释放(shmctl 还可以完成一些其他功能)。这种使用逻辑也适用于消息和信号量。示例程序代码如下: #include #include #include #include #include int main(void) { int x, shmid; int *shmptr; if((shmid=shmget(IPC_PRIVATE, sizeof(int), IPC_CREAT|0666)) < 0) printf("shmget error"), exit(1); //函数原型int shmget(key_t key,int size,int shmflg); 函数用于创建(或者获取)一 key键值指定的共享内存对象,返回该对象的系统标识符:shmid;size 是创个由

操作系统实验报告 实验二:进程通信(一)——管道及共享存 一、实验目的 ?了解进程之中相互通信的方式 ?加深对管道通信的了解 ?了解共享存通信的程序设计方法 ?了解和熟悉Linux支持的共享存储区机制 二、实验容和步骤 任务一、 (1)阅读以上父子进程利用管道进行通信的例子(例1),写出程序的运行结果并分析。 (2)编写程序:父进程利用管道将一字符串交给子进程处理。子进程读字符串,将里面的字符反向后再交给父进程,父进程最后读取并打印反向的字符串 任务二、 1)阅读例2的程序,运行一次该程序,然后用ipcs命令查看系统中共享存储区的情况,再次执行该程序,再用ipcs命令查看系统中共享存的情况,对两次的结果进行比较,并分析原因。最后用

ipcrm命令删除自己建立的共享存储区。(有关ipcs和ipcrm介绍见后面一页) (2)每个同学登陆两个窗口,先在一个窗口中运行例3程序1(或者只登陆一个窗口,先在该窗口中以后台方式运行程序1),然后在另一个窗口中运行例3程序2,观察程序的运行结果并分析。 运行结束后可以用ctrl+c结束程序1的运行。 (3)编写程序:使用系统调用shmget(),shmat(),shmdt(),shmctl(),编制程序。要求在父进程中生成一个30字节长的私有共享存段。 接下来,设置一个指向共享存段的字符指针,将一串大写字母写入到该指针指向的存贮区。调用fork()生成子进程,让子进程共享存段中的容。接着,将大写字母改成小写,子进显示程修改共享存中的容。之后,子进程将脱接共享存段并退出。父进程在睡眠5秒后,在此显示共享存段中的容(此时已经是小写字母)。 三、代码及运行结果分析 1.任务1(1) ①代码: #include #include #include #include #include #include int main() { int x,fd[2]; char buf[30],s[30]; pipe(fd); while((x=fork())==-1);

实验四进程的管道通信实验 (实验估计学时:2学时) 实验目的 1、了解什么是管道 2、熟悉UNIX/LINUX 支持的管道通信方式 实验内容 编写程序实现进程的管道通信。用系统调用pipe()建立一管道,二个子进程P1和P2分别向管道各写一句话: Child 1 is sending a message! Child 2 is sending a message! 父进程从管道中读出二个来自子进程的信息并显示(要求先接收P1,后P2)。 实验指导 一、什么是管道 UNIX系统在OS的发展上,最重要的贡献之一便是该系统首创了管道(pipe)。这也是UNIX系统的一大特色。 所谓管道,是指能够连接一个写进程和一个读进程的、并允许它们以生产者一消费者方 式进行通信的一个共享文件,又称为pipe文件。由写进程从管道的写入端(句柄1)将数据写入管道,而读进程则从管道的读出端(句柄0)读出数据。 读出端 写入端 、管道的类型: 1、有名管道 一个可以在文件系统中长期存在的、具有路径名的文件。用系统调用mknod()建立。它 克服无名管道使用上的局限性,可让更多的进程也能利用管道进行通信。因而其它进程可以知道它的存在,并能利用路径名来访问该文件。对有名管道的访问方式与访问其他文件一样, 需先用open()打开。 2、无名管道 一个临时文件。利用pipe()建立起来的无名文件(无路径名)。只用该系统调用所返回的文件描述符来标识该文件,故只有调用pipe()的进程及其子孙进程才能识别此文件描述 符,才能利用该文件(管道)进行通信。当这些进程不再使用此管道时,核心收回其索引结点。 二种管道的读写方式是相同的,本文只讲无名管道。 3、pipe文件的建立 分配磁盘和内存索引结点、为读进程分配文件表项、为写进程分配文件表项、分配用户 文件描述符 4、读/写进程互斥 内核为地址设置一个读指针和一个写指针,按先进先出顺序读、写。 为使读、写进程互斥地访问pipe文件,需使各进程互斥地访问pipe文件索引结点中的 直接地址项。因此,每次进程在访问pipe文件前,都需检查该索引文件是否已被上锁。若 是,进程便睡眠等待,否则,将其上锁,进行读/写。操作结束后解锁,并唤醒因该索引结 点上锁而睡眠的进程。

相关文档
最新文档