今天迎来元旦假期的最后一天了,过得好快~昨天跟小伙伴们在军都滑雪陪儿爽,虽说上了两回中级道都摔得异常的惨烈,但是在初级道上学习"s"转弯还是有一些小心得,可以在要往高手迈进的前提,一定得要把基本功打扎实,否则会很惨烈~好了,在这无聊的下午,用博客继续充实自己。
上次学习了System v 信号量的一些概念,并封装了一些常用方法,下面会举例用信号量来实现进程互斥,来进一步加深对信号量的认识。
先用图来描述一下这个程序的一个意图:
下面则开始实现,基于之前信号量的封装:
print.c:
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <sys/wait.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ }; #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int sem_create(key_t key) { int semid; semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666); if (semid == -1) ERR_EXIT("semget"); return semid; } int sem_open(key_t key) { int semid; semid = semget(key, 0, 0); if (semid == -1) ERR_EXIT("semget"); return semid; } int sem_setval(int semid, int val) { union semun su; su.val = val; int ret; ret = semctl(semid, 0, SETVAL, su); if (ret == -1) ERR_EXIT("sem_setval"); return 0; } int sem_getval(int semid) { int ret; ret = semctl(semid, 0, GETVAL, 0); if (ret == -1) ERR_EXIT("sem_getval"); return ret; } int sem_d(int semid) { int ret; ret = semctl(semid, 0, IPC_RMID, 0); if (ret == -1) ERR_EXIT("semctl"); return 0; } int sem_p(int semid) { struct sembuf sb = {0, -1, 0}; int ret; ret = semop(semid, &sb, 1); if (ret == -1) ERR_EXIT("semop"); return ret; } int sem_v(int semid) { struct sembuf sb = {0, 1, 0}; int ret; ret = semop(semid, &sb, 1); if (ret == -1) ERR_EXIT("semop"); return ret; } int semid; int main(int argc, char *argv[]) { semid = sem_create(IPC_PRIVATE);//由于是父子进程,所以可以创建私有的信号量集 sem_setval(semid, 0);//初始化信号量计数值为0 pid_t pid; pid = fork(); if (pid == -1) ERR_EXIT("fork"); if (pid > 0) {//父进程 } else {//子进程 } return 0; }
接下来则进行值打印:
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <sys/wait.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ }; #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int sem_create(key_t key) { int semid; semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666); if (semid == -1) ERR_EXIT("semget"); return semid; } int sem_open(key_t key) { int semid; semid = semget(key, 0, 0); if (semid == -1) ERR_EXIT("semget"); return semid; } int sem_setval(int semid, int val) { union semun su; su.val = val; int ret; ret = semctl(semid, 0, SETVAL, su); if (ret == -1) ERR_EXIT("sem_setval"); return 0; } int sem_getval(int semid) { int ret; ret = semctl(semid, 0, GETVAL, 0); if (ret == -1) ERR_EXIT("sem_getval"); return ret; } int sem_d(int semid) { int ret; ret = semctl(semid, 0, IPC_RMID, 0); if (ret == -1) ERR_EXIT("semctl"); return 0; } int sem_p(int semid) { struct sembuf sb = {0, -1, 0}; int ret; ret = semop(semid, &sb, 1); if (ret == -1) ERR_EXIT("semop"); return ret; } int sem_v(int semid) { struct sembuf sb = {0, 1, 0}; int ret; ret = semop(semid, &sb, 1); if (ret == -1) ERR_EXIT("semop"); return ret; } int semid; void print(char op_char) { int pause_time; srand(getpid());//以当前进程做为随机数的种子 int i; for (i=0; i<10; i++)//各输出十次 { sem_p(semid);//进行一个P操作 printf("%c", op_char); fflush(stdout);//由于没有用\n,所以要想在屏幕中打印出字符,需要强制清空一下缓冲区 pause_time = rand() % 3;//在0,1,2秒中随机 sleep(pause_time); printf("%c", op_char); fflush(stdout); sem_v(semid);//进行一个V操作 pause_time = rand() % 2; sleep(pause_time);//最后在0,1秒中随机 } } int main(int argc, char *argv[]) { semid = sem_create(IPC_PRIVATE);//由于是父子进程,所以可以创建私有的信号量集 sem_setval(semid, 0);//初始化信号量计数值为0 pid_t pid; pid = fork(); if (pid == -1) ERR_EXIT("fork"); if (pid > 0) {//父进程 sem_setval(semid, 1);//由于计数值初使为0,所以进行P操作时则会等待,为了进行p操作,则设置值为1 print('O'); wait(NULL);//等待子进程的退出 sem_d(semid);//最后删除信号量值 } else {//子进程 print('X'); } return 0; }
下面编译运行来看一下效果:
从运行结果来看,o跟x一定是成对出现的,不可能出现ox一起打印,这就是信号量达到互斥作用的效果。
接下来用信号集来解决哲学家就餐问题,而且这一次信号集中信号量的个数不再是1个,而是多个,关于哲学家就餐问题可参考:http://www.cnblogs.com/webor2006/p/4149323.html,怎么解决呢?
下面回归到实际代码上来,由于这次的信号集中有多个信号量,所以这个实验中就不能用之前封装的方法了,需重新编写:
dining.c:
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <sys/wait.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ }; #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int semid; int main(int argc, char *argv[]) { semid = semget(IPC_PRIVATE, 5, IPC_CREAT | 0666);//创建一个信号量集,里面包含五个信号量,这里也用私有的方式,因为会用子进程的方式模拟 if (semid == -1) ERR_EXIT("semget"); //将五个信号量的计数值都初始为1,资源都可用,模拟的是五把叉子 union semun su; su.val = 1; int i; for (i=0; i<5; i++) { semctl(semid, i, SETVAL, su); } return 0; }
而哲学家所做的事情如下:
具体实现如下:
接下来则实现wait_for_2fork()、free_2fork()两个函数:
结合图来想,就很容易明白这个算法,如下:
同样的,释放叉子类似:
至此解决哲学家就餐问题的代码就写完,下面来编译运行一下:
从中可以看到,没有出现死锁问题,下面从输出结果来分析一下:
从结果分析来看:不可能两个相邻的哲学家同时处于“吃”的状态,同时只能够有两个哲学家处于“吃”的状态。
接下来再来模拟一下死锁的情况,在模拟之前,注意:需手动将创建的信号量集给删掉,因为刚才运行是强制关闭程序的,另外在实现之前,需要思考一下怎么样能产生死锁,其实思路很简单,就是申请叉子的时候,一个个申请,而不是当只有两个都有的情况下才能申请,所以,修改代码如下:
接下来实现wait_1fork():
下面编译运行:
从结果来看确实是阻塞了,由于都拿起了左边的叉子,而且都在等待右边叉子,而都没人释放左叉子,于是乎死锁就产生了。
最后贴上完整代码:
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <sys/wait.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ }; #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) #define DELAY (rand() % 5 + 1)//定义一个睡眠时间,1~5秒中 int semid; //等待一把叉子 int wait_1fork(int no) { struct sembuf sb = {no, -1, 0}; int ret; ret = semop(semid, &sb, 1); if (ret == -1) ERR_EXIT("semop"); return ret; } //等待左右两个叉子 void wait_for_2fork(int no) { int left = no; int right = (no + 1) % 5; struct sembuf buf[2] = { {left, -1, 0}, {right, -1, 0} }; semop(semid, buf, 2); } //释放左右两信叉子 void free_2fork(int no) { 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) { srand(getpid());//设置随机的种子 for (;;) {//不断循环执行 /* printf("%d is thinking\n", no);//首先思考 sleep(DELAY); printf("%d is hungry\n", no);//饿了 wait_for_2fork(no); printf("%d is eating\n", no);//当获取到了左右两把叉子,则开吃 sleep(DELAY); free_2fork(no);//吃完则放下左右两把叉子 */ int left = no; int right = (no + 1) % 5; printf("%d is thinking\n", no); sleep(DELAY); printf("%d is hungry\n", no); wait_1fork(left); sleep(DELAY); wait_1fork(right); printf("%d is eating\n", no); sleep(DELAY); free_2fork(no); } } int main(int argc, char *argv[]) { semid = semget(IPC_PRIVATE, 5, IPC_CREAT | 0666);//创建一个信号量集,里面包含五个信号量,这里也用私有的方式,因为会用子进程的方式模拟 if (semid == -1) ERR_EXIT("semget"); //将五个信号量的计数值都初始为1,资源都可用,模拟的是五把叉子 union semun su; su.val = 1; int i; for (i=0; i<5; i++) { semctl(semid, i, SETVAL, su); } //接下来创建四个子进程,加上父进程则为5个,来模拟5个哲学家 int no = 0; pid_t pid; for (i=1; i<5; i++) { pid = fork(); if (pid == -1) ERR_EXIT("fork"); if (pid == 0) { no = i; break; } } philosophere(no); return 0; }
好了,今天学到这,肚子饿了,吃饭下次继续~