您的当前位置:首页正文

设计二

2021-02-17 来源:欧得旅游网
设计二: 设计任务:

掌握进程的管道通讯机制和信号量同步互斥机制。 1. 进程的管道通讯

编制一个程序,程序中创建一个子进程。然后父子进程各自独立运行,父进程不断地在标准输入设备上读入小写字母,写入管道。子进程不断地从管道中读取字符,转换为大写字母后输出到标准输出设备上。当读到x时,结束。 2. 信号量实现的同步互斥机制

编制一个程序,程序中创建5个子进程,代表五位哲学家,然后父进程结束。使用信号量机制解决哲学家进餐问题。当哲学家进餐时,屏幕输出: [进程号] eating!

当哲学家思考时,屏幕输出: [进程号] thinging!

相关的系统调用和函数:pipe(); write(); read(); semget(); sepop(); semctl();

要求:查找并阅读上述系统调用的相关资料,将上述相关的函数封装为P( )、V( )操作,使用你封装的P( )、V( )操作实现5位哲学家的同步和互斥。

一 程序清单

1 pip.c

#include #include #include

#define BUFSIZE 10

int main(void) {

char ch,dh,eh;

int p[2];//文件描述符 pid_t childpid;

}

if(pipe(p) == -1)//创建管道 { perror(\"pipe call\"); return -1; }

if((childpid = fork()) == -1)//创建子进程 { perror(\"fork call\"); return -1; }

if(childpid!=0)//父进程 { close(p[0]);//关闭读文件 do { ch = getchar(); write(p[1],&ch,1);//向管道写 }while(ch!='x');//遇到'x'则结束 }

else if(childpid==0)//子进程 { close(p[1]);//关闭写文件 while(1) { read(p[0],&dh,1);//从管道读 if(dh == 'x') { printf(\"\\n\"); return 0; } else if(dh>='a'&&dh<='z') { eh = (char)((int)dh - 32);//转化成大写输出 printf(\"%c\ } else { printf(\"%c\ } } }

2 pilosopher.c

#include #include #include #include #include #include #include #include #include #include #include

#define N 5 //哲学家个数

#define LEFT (i+N-1)%N //i左边的哲学家 #define RIGHT (i+1)%N //i右边的哲学家 #define THINKING 0 #define HUNGRY 1 #define EATING 2

//共享内存的结构 typedef struct{

int state[N];//哲学家的状态 int count;//就餐哲学家的个数 } shared_sum;

static int semid;//标记信号量集

static shared_sum *sharedsum;//实现共享内存的指针

//创建编号为0~N的N+1个信号量,第N个信号量为MUTEX信号量 void create() {

int semnum;

if((semid = semget(IPC_PRIVATE,N+1,0666))==-1)//创建信号量集 perror(\"failed to create semaphore\"); for(semnum=0;semnum<=N;semnum++) { if(semctl(semid,semnum,SETVAL,1)==-1)//初始化各信号量 perror(\"failed to set semaphore\"); } }

//开辟共享内存区,存储哲学家状态

int shmem() {

int shid; int num;

shid = shmget(IPC_PRIVATE,sizeof(shared_sum),IPC_CREAT|0666);//开辟内存区

if(shid ==-1) { if(((shid = shmget(IPC_PRIVATE,sizeof(shared_sum),IPC_CREAT|0664))==-1)|| ((sharedsum = (shared_sum *)shmat(shid,NULL,0)) == (void *)-1)) { return -1; } } else { sharedsum = (shared_sum *)shmat(shid,NULL,0);//内存区数据关联 if(sharedsum ==(void *)-1) return -1; //共享内存初始化 for(num=0;numstate[num] = 0; sharedsum->count = 0; }

return 0; }

//某个信号量的up操作 void up(int num) {

struct sembuf v_buf = {num,1,SEM_UNDO};

semop(semid,&v_buf,1); }

//某个信号量的down操作 void down(int num) {

struct sembuf p_buf = {num,-1,SEM_UNDO};

semop(semid,&p_buf,1); }

//检查是否可吃 void test(int i) {

int count; int j;

if(sharedsum->state[i] == HUNGRY && sharedsum->state[LEFT]!=EATING && sharedsum->state[RIGHT]!=EATING) { sharedsum->state[i] = EATING; up(i); printf(\"philosopher \"); for(j=0;jstate[j] == EATING) printf(\"%d \ } printf(\"now are eating\\n\"); } }

//拿起叉子

void take_forks(int i) {

down(N);//进入临界区

sharedsum->state[i] = HUNGRY; test(i);//尝试获取两把叉子 up(N);//离开临界区

down(i);//如果得不到需要的叉子则阻塞 }

//放下叉子

void put_forks(int i) {

down(N);//进入临界区

sharedsum->state[i] = THINKING;

test(LEFT);//检查左边的哲学家现在可以吃吗 test(RIGHT);//检查右边的哲学家现在可以吃吗 up(N);//离开临界区 }

//代表一个哲学家 void philosopher(int i) {

int j; time_t t;

srand((unsigned) time(&t));//初始化随机数发生器 for(j=0;j<10;j++) { printf(\"philosopher %d is thinking\\n\ take_forks(i); //要求得到两把叉子或被阻塞 usleep(rand() % 100);//随机睡眠0~100毫秒 put_forks(i);//把两把叉子入回桌子

} }

int main() {

pid_t pid[5]; int i; create();

if(shmem()==-1) { return -1; }

for(i=0;i<5;i++)//创建5个哲学家 { pid[i]=fork(); if(pid[i] == 0) { philosopher(i);//运行哲学家函数 return 0; } }

for(i=0;i<5;i++) wait();//等待子进程结束 return 0; }

二 设计概述

1管道通信的设计。 (1)算法思想

创建一个管道,得到两个文件描述符,一个分配给父进程用于向管道写数据,一个分配给子进程用于从管道读数据。当父进程在运行时,关闭读数据一端,让子进程进入阻塞;因为父进程写数据是通过getchar()实现的,当输入回车时,该函数不能得到字符,这时子进程得以运行,关闭写端口,使父进程进入阻塞,从管道读取数据,并把小写字母转换成大写字母后输出。这样就实现了父进程和子进程轮流运行,以写一行就读一行的形式显示程序结果。

(2)系统调用:read,write

2信号量解决哲学家就餐问题。

(1) 算法思想:

哲学家状态跟踪:使用一个数组state跟踪每个哲学家的状态是在进餐,思考还是饥饿(正在试图拿叉子),并把state数组设为共享内存,使之对所有哲学家都是可见的,一个哲学家只有在两个邻居都没有进餐时才允许进入进餐状态。

哲学家行为控制:每个哲学家分配一个信号量,这样在所需的叉子被占用时,想进餐的哲学家就被阻塞。

临界区控制:本程序中,叉子是受互斥访问的有限资源,所以对叉子的所有操作,包括拿叉子和放回叉子都是临界区,使用一个二元信号量对临界区进行保护。

检查每个时刻就餐的哲学家:每当一个哲学家状态为EAIING时,检查此时刻所有状态为EATING的哲学家,实际上最多只有2个,并输出这些哲学家的编号。 (2) 算法优点:

本算法不仅没有死锁,而且对于任意位哲学家的情况都能获得最大的并行度。因为实现了对每位哲学家的状态跟踪。

(3) 数据结构 //共享内存的结构 typedef struct{

int state[N];//哲学家状态数组 int count;//就餐的哲学家个数

} shared_sum;

struct sembuf v_buf //用于对信号量集中的一个信号量进行up,down操作

(4) 系统API调用

Semget,semctl,semop,shmget,shmat

三 运行结果

1. 进程的管道通信

2哲学家进餐问题

为了使结果能够反应了算法的本质,每次EATING的哲学家有变动时,都输出新的EATING的哲学家,这样更能体现本程序的准确性。

四 心得体会

通过第一个子任务理解了进程的管道通信机制和实现方法,特别是体会到管道也是一种文件,只不过这种文件比较特殊,可以有两个不同的文件描述符,用来实现读写的交互。通过子任务一我学会了管道的读写操作,并以此完成进程的通信。

通过第二个子任务哲学家就餐问题,我学会了信号量的使用和共享内存的方法,体会到了进程间通信的方法,体会到了操作系统编程的特点。这和别的编程题不一样,要好好理解用到的API的功能和调用时机,因为这些函数不仅仅是算法上的作用,更重要的是它涉及到了原子操作的封装和内存上的操作等底层的技术。

因篇幅问题不能全部显示,请点此查看更多更全内容