哈工大操作系统实验——信号量
实验入口
https://www.lanqiao.cn/courses/115
主要参考文章
https://www.dotcpp.com/course/461 lseek()函数:用于移动打开文件的指针
https://cloud.tencent.com/developer/article/1425052 linux系统调用之write源码解析(基于linux0.11)
https://blog.youkuaiyun.com/yuanren201/article/details/103207910 get_fs_bytes解析
https://blog.youkuaiyun.com/to_free/article/details/115187981 VIM与系统剪贴板的复制粘贴
https://zhuanlan.zhihu.com/p/428283092 操作系统实验六 信号量的实现和应用(哈工大李治军)
https://blog.youkuaiyun.com/weixin_43987915/article/details/108949942 哈工大操作系统实验6 信号量的实现 pc.c 编译时报错 对‘sem_open‘未定义的引用
https://blog.youkuaiyun.com/qq_59804059/article/details/120634348 Linux 文件编程 open函数
https://blog.youkuaiyun.com/qq_42518941/article/details/119757885 哈工大-操作系统-HitOSlab-李治军-实验5-信号量的实现和应用
任务一 实现pc.c
在 Ubuntu 上编写应用程序“pc.c”,解决经典的生产者—消费者问题,完成下面的功能:
1.建立一个生产者进程,N 个消费者进程(N>1);
2.用文件建立一个共享缓冲区;
3.生产者进程依次向缓冲区写入整数 0,1,2,...,M,M>=500;
4.消费者进程从缓冲区读数,每次读一个,并将读出的数字从缓冲区删除,然后将本进程 ID 和 + 数字输出到标准输出;
5.缓冲区同时最多只能保存 10 个数。
其中 ID 的顺序会有较大变化,但冒号后的数字一定是从 0 开始递增加一的。
先附上我的代码吧【注:我没做到从缓冲区删除,但其他都完成了】
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
const char* filename="buffer.txt";
sem_t* empty;
sem_t* full;
sem_t* mutex;
int fd;
void Producer(){
int i;
char s[5]={0};
for(i=0;i<=500;i++){
sem_wait(empty);
sem_wait(mutex);
int tmp=lseek(fd,0,SEEK_CUR);
lseek(fd,0,SEEK_END);
sprintf(s,"%03d\n",i);
write(fd,s,4);
lseek(fd,tmp,SEEK_SET);
sem_post(mutex);
sem_post(full);
}
}
void Consumer(){
sem_wait(full);
sem_wait(mutex);
char s[5]={0};
read(fd,s,4);
printf("%d : %s",getpid(),s);
sem_post(mutex);
sem_post(empty);
}
int main(){
sem_unlink("empty");
sem_unlink("full");
sem_unlink("mutex");
fd=open(filename,O_RDWR|O_CREAT);
printf("%d\n",errno);
empty=sem_open("empty",O_CREAT,0644,10);
full=sem_open("full",O_CREAT,0644,0);
mutex=sem_open("mutex",O_CREAT,0644,1);
if(!fork()){
Producer();
return 0;
}
int i;
for(i=0;i<10;i++){
if(!fork()){
while(1) Consumer();
}
}
close(fd);
return 0;
}
要点1 系统调用的IO读写
这部分耗费了我海量时间,主要原因还是因为我没有好好学就直接上手写导致很多地方都因为不清楚而寄了。。。
先大致讲讲文件读写的原理吧。打开一个文件作为数据流,有一个文件指针,该指针指向的地方就是之后读写开始的地方,读写还有lseek都可以让指针移动。
再放个各个系统调用的签名。
@param 文件名 模式
@return 所需文件描述符
int open(char* filename,int flag);
其中flag的可能取值:
如果想要多个方式并行,则可以用|连接。【联系一下原理,这大概是用了标志位吧,每个标志只有一位是1】
这部分踩过的坑:
① 选择O_CREAT,如果文件已经存在,居然是会报错?【表现为errno=13,还会输出一堆奇怪的东西】
@param 文件描述符 写入字符串 写入长度
@return 报错信息
int read(int fd,char* string,size_t size);
read会读出size个字节然后存进string里面,同时也会移动文件指针向前size个字节。
@param 文件描述符 写入字符串 写入长度
@return 报错信息
int write(int fd,char* string,size_t size);
基本同write。
这部分踩过的坑:
write(fd,NULL,0) ——合法
write(fd,NULL,a),a>0 ——寄!
这还是因为write的具体实现了。
write里面有个判断
而get_fs_byte:
确实感觉空的话挺危险的【】
@param 文件描述符
@return 报错信息
int close(fd);
这个没啥好说的,记得关就是了
要点2 信号量的调用
这方面看linux自带的man文档就行,写得很清楚。
输入指令:
man sem_overview
这部分踩过的坑:
千万注意最后不使用信号量时要释放,使用sem_unlink。不然最后的输出结果会非常诡异。
要点3 编写程序
以上差不多就是涉及到的需要自己了解的课外知识点了,接下来就需要自己编写程序。
总体框架就按它给的差不多:
Producer()
{
// 空闲缓存资源
P(Empty);
// 互斥信号量
P(Mutex);
//生产并放一个item进缓冲区
V(Mutex);
V(Full);
}
Consumer()
{
P(Full);
P(Mutex);
//从缓存区取出一个赋值给item并消费;
V(Mutex);
V(Empty);
}
有个点挺有趣的,就是它实际上把文件指针也看成一种资源了,因此也需要在同步段对其进行更新。
printf的stdout也是资源。
故以上两者都只能在锁内同步段进行更新。
main函数就照本宣科地用fork建立子进程就行。
实验二 自己实现信号量
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 下,测试自己实现的信号量。
由于不小心写完的实验代码被销毁了,因此差不多参考的是这篇文章【戳这里】,修改了一些地方,构成了我的回忆版代码。
下面代码都经过测试通过,可以放心食用~
要点1 系统调用修改
详见文章,写得很清楚。
要点2 sem.c文件的编写
sem_t定义
/* 定义的信号量数据结构: */
# ifndef _SEM_H_
# define _SEM_H_
#include<linux/sched.h>
typedef struct semaphore_t
{
char name[20];/* 信号量的名称 */
int value; /* 信号量的值 */
int active;//我自己加的,是对象池思想,感觉写得还挺好的2333
struct tast_struct *queue;/* 指向阻塞队列的指针 */
} sem_t;
#endif
#include <unistd.h>
#include <linux/sem.h>
#