实验六 信号量的实现和应用
实验目的
加深对进程同步与互斥概念的认识;
掌握信号量的使用,并应用它解决生产者——消费者问题;
掌握信号量的实现原理。
实验内容
本次实验的基本内容是:
在 Ubuntu 下编写程序,用信号量解决生产者——消费者问题;
在 0.11 中实现信号量,用生产者—消费者程序检验之。
用信号量解决生产者—消费者问题
在 Ubuntu 上编写应用程序“pc.c”,解决经典的生产者—消费者问题,完成下面的功能:
建立一个生产者进程,N 个消费者进程(N>1);
用文件建立一个共享缓冲区;
生产者进程依次向缓冲区写入整数 0,1,2,…,M,M>=500;
消费者进程从缓冲区读数,每次读一个,并将读出的数字从缓冲区删除,然后将本进程 ID 和 + 数字输出到标准输出;
缓冲区同时最多只能保存 10 个数。
一种可能的输出效果是:
10: 0 10: 1 10: 2 10: 3 10: 4 11: 5 11: 6 12: 7 10: 8 12: 9 12: 10 12: 11 12: 12 …… 11: 498 11: 499
其中 ID 的顺序会有较大变化,但冒号后的数字一定是从 0 开始递增加一的。
pc.c 中将会用到 sem_open()、sem_close()、sem_wait() 和 sem_post() 等信号量相关的系统调用,请查阅相关文档。
实现信号量
Linux 在 0.11 版还没有实现信号量,Linus 把这件富有挑战的工作留给了你。如果能实现一套山寨版的完全符合 POSIX 规范的信号量,无疑是很有成就感的。但时间暂时不允许我们这么做,所以先弄一套缩水版的类 POSIX 信号量,它的函数原型和标准并不完全相同,而且只包含如下系统调用:
sem_t *sem_open(const char *name, unsigned int value); int sem_wait(sem_t *sem); int sem_post(sem_t *sem); int sem_unlink(const char *name);
sem_open() 的功能是创建一个信号量,或打开一个已经存在的信号量。
sem_t 是信号量类型,根据实现的需要自定义。
name 是信号量的名字。不同的进程可以通过提供同样的 name 而共享同一个信号量。如果该信号量不存在,就创建新的名为 name 的信号量;如果存在,就打开已经存在的名为 name 的信号量。
value 是信号量的初值,仅当新建信号量时,此参数才有效,其余情况下它被忽略。当成功时,返回值是该信号量的唯一标识(比如,在内核的地址、ID 等),由另两个系统调用使用。如失败,返回值是 NULL。
sem_wait() 就是信号量的 P 原子操作。如果继续运行的条件不满足,则令调用进程等待在信号量 sem 上。返回 0 表示成功,返回 -1 表示失败。
sem_post() 就是信号量的 V 原子操作。如果有等待 sem 的进程,它会唤醒其中的一个。返回 0 表示成功,返回 -1 表示失败。
sem_unlink() 的功能是删除名为 name 的信号量。返回 0 表示成功,返回 -1 表示失败。
在 kernel 目录下新建 sem.c 文件实现如上功能。然后将 pc.c 从 Ubuntu 移植到 0.11 下,测试自己实现的信号量。
什么是信号量?
信号量,英文为 semaphore,最早由荷兰科学家、图灵奖获得者 E. W. Dijkstra 设计,任何操作系统教科书的“进程同步”部分都会有详细叙述。
Linux 的信号量秉承 POSIX 规范,用man sem_overview可以查看相关信息。
本次实验涉及到的信号量系统调用包括:sem_open()、sem_wait()、sem_post() 和 sem_unlink()。
生产者—消费者问题
生产者—消费者问题的解法几乎在所有操作系统教科书上都有,其基本结构为:
Producer() { // 生产一个产品 item; // 空闲缓存资源 P(Empty); // 互斥信号量 P(Mutex); // 将item放到空闲缓存中; V(Mutex); // 产品资源 V(Full); } Consumer() { P(Full); P(Mutex); //从缓存区取出一个赋值给item; V(Mutex); // 消费产品item; V(Empty); }
显然在演示这一过程时需要创建两类进程,一类执行函数 Producer(),另一类执行函数 Consumer()。
思路
大家可以参考这个演示视频,先总体梳理一遍。如果实验三系统调用学的不错的话,这里的系统调用编写和编译应该是很清晰的。
通俗的讲,在用户程序中想要使用内核态中的系统调用命令,需要在用户态执行对系统调用命令的调用
通过宏展开
由此通过著名的 int 0x80 中断进入内核态