一、实验目的:
掌握基本的同步算法,理解经典进程同步问题的本质;学习使用Linux的进程同步机制,掌握相关API的使用方法;能利用信号量机制,采用多种同步算法实现不会发生死锁的哲学家进餐程序。
二、实验平台:
虚拟机:VMWare12
操作系统:kali linux
编辑器:Gedit
编译器:Gcc
三、实验内容:
(1)以哲学家进餐模型为依据,在Linux控制台环境下创建5个进程,用semget函数创建一个信号量集(5个信号量,初值为1),模拟哲学家的思考和进餐行为:每一位哲学家饥饿时,先拿起左手筷子,再拿起右手筷子;筷子是临界资源,为每一支筷子定义1个互斥信号量;想拿到筷子需要先对信号量做P操作,使用完释放筷子对信号量做V操作。
伪代码描述:
semaphore chopstick[5]={1,1,1,1,1};
•第i位哲学家的活动可描述为:
printf("%d is thinking\\ni);
printf("%d is hungry\\ni);
[i])拿左筷子
[(i+1) % 5])拿右筷子
printf("%d is eating\\ni);
[i])放左筷子
[(i+1) % 5])放右筷子
…
}while[true];
运行该组进程,观察进程是否能一直运行下去,若停滞则发生了什么现象?并分析原因。
(2)解决哲学家进餐问题可采用如下方法:a.仅当哲学家的左、右两只筷子均可用时,才允许他拿起筷子进餐;b.至多只允许有4位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐;c.规定奇数号哲学家先拿起他左手的筷子,然后再拿起他右手的筷子,而偶数号哲学家则先拿起他右手的筷子,然后再拿起他左手的筷子。方法a在示例程序中给出,请用方法b和c写出不会发生死锁的哲学家进餐程序。
(3)设计程序,实现生产者/消费者进程(线程)的同步与互斥。在该程序中创建4个进程(或线程)模拟生产者和消费者,实现进程(线程)的同步与互斥。
四、实验步骤:
一、Linux的信号量机制
为了简化对多个信号量的操作,Linux系统中提出了信号量集的概念。一个信号量集对象中可以容纳多个信号量,System V 信号量的分配和操作是以信号量集为单位的。
1. 进程利用信号量获得共享资源的步骤:
a.测试控制该资源的信号量;
b.若信号量为正,则进程可以使用该资源;
使用资源时,进程将该信号量减1(P操作);
不再使用资源时,进程将该信号量值加1(V操作);
c.若信号量为0,则进程进入睡眠状态
P操作和V操作都原子操作。
2. 创建或打开信号量集
semget()系统调用创建一个新信号量集或获取一个既有信号量集的表示符。
函数原型:
#include int semget(key_t key, int nsems, int flag); 返回值:成功返回信号量集ID,出错返回-1。 nsems信号量集中信号量个数;flags:IPC_CREAT,IPC_EXCL,权限组合; 第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。 第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。 注意:建立信号量集时每个信号量的初始值不确定。 3. 信号量控制操作 semctl()系统调用在一个信号量集或集合中的单个信号量上执行各种控制操作。 函数原型: #include int semctl (int semid, int semnum, int cmd, …/*union semun arg*/) semnum:信号量编号或0,表示对指定信号量做控制操作; cmd: 操作命令,实施的控制操作; IPC_SET 设置信号量集的属性 IPC_RMID 删除信号量集 SETVAL 设置semnum信号量的值 SETALL 设置所有信号量的初始值 union semun{ int val; struct semid_ds *buf; unsigned short *array; } 例:定义一个信号量集,含3个信号量,初始值分别为(2,5,1)。 semid = semget(IPC_PRIVATE,3,IPC_CREAT|IPC_EXCL|0777); unsigned short vals[3] = {2,5,1}; union semnu se; se.array = vals; semclt(semid,0,SETALL,se); 4. 信号量集操作 semop()系统调用在semid标识的信号量集中的信号量上执行一个或多个up或down操作,可用于进程间的同步和互斥。 函数原型: #include int semop(int semid, struct sembuf *semop, size_t nops); 返回:成功返回0,出错返回-1。 Struct sembuf{ sem_op; /*operation(negative, 0, positive*/ short sem_flg; /*IPC_NOWAIT,SEM_UNDO*/ } 例:对上文定义的信号量集semid中的3个信号量,分别执行如下操作:1: p(1); 2: p(3); 3: v(3); 其semop的操作语句为: struct sembuf ops[3] = {{0, -1, SEM_UNDO}, {1,-3, SEM_UNDO},{2,3, SEM_UNDO}}; semop(semid, ops ,3); 5.组合权限说明 0666 文件或目录的所有者、所有者所在组、其他用户对该对象均可读、可写rw-rw-rw- 0777 文件或目录的所有者、所有者所在组、其他用户对该对象均可读、可写、可执行 rwxrwxrwx 附:示例程序(左、右两只筷子均可用时才允许拿起筷子) #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _SEM_SEMUN_UNDEFINED union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; }; #endif #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int wait_1chopstick(int no,int semid) { struct sembuf sb = {no,-1,0}; int ret; ret = semop(semid,&sb,1); if(ret < 0) { ERR_EXIT("semop"); } return ret; } int free_1chopstick(int no,int semid) { struct sembuf sb = {no,1,0}; int ret; ret = semop(semid,&sb,1); if(ret < 0) { ERR_EXIT("semop"); } return ret; } #define DELAY (rand() % 5 + 1) void wait_for_2chopstick(int no,int semid) { int left = no; int right = (no + 1) % 5; struct sembuf buf[2] = { {left,-1,0}, {right,-1,0} }; semop(semid,buf,2); } void free_2chopstick(int no,int semid) { int left = no; int right = (no + 1) % 5; struct sembuf buf[2] = { {left,1,0}, {right,1,0} }; semop(semid,buf,2); } void philosophere(int no,int semid) { srand(getpid()); for(;;) { #if 0 printf("%d is thinking\\n",no); sleep(DELAY); printf("%d is hungry\\n",no); wait_for_2chopstick(no,semid); printf("%d is eating\\n",no); sleep(DELAY); free_2chopstick(no,semid); #else int left = no; int right = (no + 1) % 5; printf("%d is thinking\\n",no); sleep(DELAY); printf("%d is hungry\\n",no); wait_1chopstick(left,semid); sleep(DELAY); wait_1chopstick(right,semid); printf("%d is eating\\n",no); sleep(DELAY); free_1chopstick(left,semid); free_1chopstick(right,semid); #endif } } int main(int argc,char *argv[]) { int semid; semid = semget(IPC_PRIVATE,5,IPC_CREAT | 0666); if(semid < 0) { ERR_EXIT("semid"); } union semun su; su.val = 1; int i; for(i = 0;i < 5;++i) { semctl(semid,i,SETVAL,su); } int num = 0; pid_t pid; for(i = 1;i < 5;++i) { pid = fork(); if(pid < 0) { ERR_EXIT("fork"); } if(0 == pid) { num = i; break; } } philosophere(num,semid); return 0; } 五、实验结果
第四个参数是union semuncmd参数: IPC_STAT 获取信号量集的属性 GETVAL 返回semnum信号量的值 GETALL 获取所有信号量的值