操作系统实验6之信号量的实现与应用
- 操作系统中常用信号量相关系统调用函数用法
1、sem_open:用于创建或打开一个命名的信号量。
点击查看代码
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
name:信号量的名称,必须以斜杠开头,例如/my_semaphore。
oflag:打开标志,可以为O_CREAT(创建信号量)和O_EXCL(只在信号量不存在时创建)的组合,或者为0(打开现有信号量)。
mode:信号量的访问权限,通常使用S_IRUSR | S_IWUSR表示用户可读写,并且可以通过’或|‘进行组合可选项如下:
S_IRUSR:用户(拥有者)具有读权限。
S_IWUSR:用户(拥有者)具有写权限。
S_IRGRP:组成员具有读权限。
S_IWGRP:组成员具有写权限。
S_IROTH:其他用户具有读权限。
S_IWOTH:其他用户具有写权限。
value:信号量的初始值(计数器的初始值)。
sem_open函数返回一个指向信号量的指针,可以在后续的信号量操作中使用该指针。
2、sem_close:关闭一个已经打开的信号量。
点击查看代码
#include <semaphore.h>
int sem_close(sem_t *sem);
sem:指向要关闭的信号量的指针。
3、sem_wait:等待信号量,并对其进行P(原子减一)操作。
点击查看代码
#include <semaphore.h>
int sem_wait(sem_t *sem);
sem:指向要等待的信号量的指针。
返回值为0表示成功
4、sem_post:对信号量进行V(原子加一)操作。
点击查看代码
#include <semaphore.h>
int sem_post(sem_t *sem);
sem:指向要发布的信号量的指针。
sem_post函数会将信号量的值加一。如果有其他进程正在等待该信号量,则其中一个进程将被唤醒。
5、sem_unlink:用于删除(取消关联)一个命名信号量。
点击查看代码
#include <fcntl.h>
int sem_unlink(const char *name);
返回值为0表示删除成功
特别注意:在使用以上系统调用时,编译时需要加上-pthread,以确保正确链接线程和实时库
本次操作系统实验分为两部分:
(1)在Ubuntu下实现生产者消费者进程:一个生产者进程,N个消费者进程,生产者进程产生0,1,2...N,存放在共享缓冲区中,消费者从共享缓冲区中取出数字并打印:进程号:数字。要求缓冲区使用文件,缓冲区最多存储10个数字
(2)在Linux0.11中实现信号量,并用上面的生产者消费者程序检验
- 在Ubuntu下实现生产者消费者问题
第一部分主要涉及知识点:信号量、文件操作相关系统调用的使用
点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <fcntl.h>
#define __LIBRARY__
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h> //系统调用wait函数头文件
_syscall2(sem_t, sem_open, const char *, name, unsigned int, value);
_syscall1(int, sem_wait, sem_t *, sem);
_syscall1(int, sem_post, sem_t *, sem);
_syscall1(int, sem_unlink, const char *, name);
#define N 5 //消费者进程个数
#define BUFFER_SIZE 10
#define BUFFER_FILE "buffer.txt"
#define MAX_NUM 520
int item = 0;
sem_t *empty_slots, *full_slots, *mutex; //用于接受sem_open函数创建信号量的返回值
int write_pos = 0; //定义生产者进程在缓冲区中的指针
void Consumer(int fd)
{
//static int cur_num = 0;
int read_pos;
int read_buf;
while(1)
{
sem_wait(full_slots);
sem_wait(mutex);
lseek(fd, BUFFER_SIZE * sizeof(int), SEEK_SET); //调整文件指针到末尾
ssize_t count = read(fd, &read_pos, sizeof(int)); //读取缓冲区读位置
if(count < 0)
{
printf("erro!\n");
exit(-1);
}
//读取缓冲区内容
lseek(fd, read_pos * sizeof(int), SEEK_SET);
read(fd, &read_buf, sizeof(int));
printf("%d: %d\n", getpid(), read_buf);
//调整读取指针并写入
read_pos++;
read_pos %= 10;
lseek(fd, BUFFER_SIZE * sizeof(int), SEEK_SET);
write(fd, (char *)&read_pos, sizeof(int));
sem_post(mutex);
sem_post(empty_slots);
}
exit(0);
}
void Producer(int fd)
{
for(int i = 0; i < MAX_NUM; i++)
{
sem_wait(empty_slots); //对缓冲区剩余空间进行p操作
sem_wait(mutex); //进行临界区保护
lseek(fd, write_pos * sizeof(int), SEEK_SET); //移动指针
write(fd, (char *)&i, sizeof(int)); //写入
write_pos++;
write_pos %= 10; //调整指针
sem_post(mutex);
sem_post(full_slots); //执行p操作
}
exit(0);
}
int main()
{
pid_t p; //用于接收fork返回值
int out = 0;
int fd;
int i;
//初始化信号量
mutex = sem_open("mutex", 1);
if(mutex == NULL)
{
printf("errno: %s\n",strerror(errno));
return -1;
}
empty_slots = sem_open("empty", BUFFER_SIZE);
if(empty_slots == NULL)
{
printf("errno: %s\n",strerror(errno));
return -1;
}
full_slots = sem_open("full", 0);
if(full_slots == NULL)
{
printf("errno: %s\n",strerror(errno));
return -1;
}
//创建并打开文件
fd = open(BUFFER_FILE, O_RDWR | O_CREAT | O_TRUNC, 0644);
if(fd < 0)
{
perror("file create failed: ");
}
else
{
printf("%s create successfully.\n", BUFFER_FILE);
}
/*使用fork分别创建消费者进程和生产者进程
不能使用pthread_create函数,因此Linux0.11没有提供线程,只有进程,
最后也还要在Linux0.11中运行
*/
lseek(fd,BUFFER_SIZE * sizeof(int), SEEK_SET); //调整指针到缓冲区的末尾并写入out指针位置
write(fd, (char *)&out, sizeof(int));
// int read_out = -1;
// lseek(fd, BUFFER_SIZE * sizeof(int), SEEK_SET);
// read(fd, &read_out, sizeof(int));
// printf("read out = %d\n", read_out);
//创建生产者进程
if((p = fork()) == 0)
{
printf("create producer successful! producer pid = %d\n", getpid());
Producer(fd);
exit(0); //执行完成后退出,结束,否则后续的代码继续执行会导致在生产者进程中fork消费者进程
}
//创建消费者进程
for(i = 0; i < N; i++)
{
if((p = fork()) == 0)
{
printf("create Consumer successful! consumer pid = %d\n", getpid());
Consumer(fd);
exit(0);
}
}
//父进程等待子进程结束
for(i = 0; i < N + 1; i++)
{
pid_t pid = wait(NULL);
printf("%d exit!\n", pid);
}
printf("all child process terminate.\n");
//关闭信号量
if(sem_unlink("empty") == 0)
{
printf("empty unlinked successfully\n");
}
else
{
perror("empty Failed to unlink semaphore");
}
if(sem_unlink("full") == 0)
{
printf("full unlinked successfully\n");
}
else
{
perror("full Failed to unlink semaphore");
}
if(sem_unlink("mutex") == 0)
{
printf("/mutex unlinked successfully\n");
}
else
{
perror("mutex Failed to unlink semaphore");
}
//关闭文件
if(close(fd) == 0)
{
printf("close %s file successfully.\n", BUFFER_FILE);
}
else
{
perror("close file failed.");
}
return 0;
}
注意:在编译的时候需要加上-pthread才能链接到动态链接库
- 在Linux0.11中实现信号量
思考我们在使用系统调用时,操作系统是如何一步步执行进入到内核执行相应的内核函数的
以sem_t* sem_open(const char *name, int value)为例:
首先使用了unistd.h头文件中的宏_syscall2创建sem_open函数的定义,这个函数封装了这个系统调用函数的系统调用号并把相应的参数传递给寄存器,同时设置承接返回值的寄存器,然后int 0x80触发中断进入内核。在内核中会调用system_call函数,这个函数首先将系统调用号跟最大系统调用号做比较,然后保存现场,将参数压栈,随后根据系统调用号在系统调用函数指针数组中找到相应的内核系统调用函数,随后跳转到对应的系统调用函数执行,执行完毕后返回system_call函数,再返回系统调用,最后返回sem_open下一条指令处。
分析上面系统调用的过程,我们要实现信号量,可以使用倒序自底向上考虑:
(1)在kernel/文件夹下创建一个sys_sem.c文件,实现信号量相关函数sys_open、sys_wait、sys_post、sys_unlink;
(2)信号量不是C语言的标准变量,因此我们要通过结构体实现信号量的结构体定义,这个可以写在/include/unistd.h头文件中。既然要自己定义信号量结构体,那就必须为信号量提供一些操作函数,因此在(1)中还要实现init_queue、is_empty等操作函数。操作上,应该先实现(2)再实现(1),但是思考上通常来说会先想到(1);
(3)在system_call中会比较系统调用号,增加了系统调用那么肯定是在原有系统调用号基础上继续排号,因此要修改system_call.s中最大系统调用号,同时需要在unistd.h文件中增加相应的系统调用号宏定义。比较了系统调用号后会根据系统调用号从系统调用函数数组中找到相应的系统调用函数,那么就需要将增加的系统调用函数增加到系统调用指针数组中,因此需要修改/include/linux/sys.h文件,增加系统调用函数声明和增加函数指针数组内容;
(4)在应用程序中会根据系统调用号封装int 0x80进入内核,同时也会使用到信号量,因此要在应用层的/include/unistd.h中增加和内核中unistd.h同样的信号量定义和系统调用号;
具体代码如下:
(1)内核中/include/unistd.h修改内容
点击查看代码
#define __NR_sem_open 72 //添加系统调用号,顺序要和系统调用表能够对的上
#define __NR_sem_wait 73
#define __NR_sem_post 74
#define __NR_sem_unlink 75
//定义信号量相关结构体
#define QUE_LEN 16 //信号量中最长等待队列长度
#define SEM_FAILED (void *) 0;
struct semaphore_queue
{
int front; //队列头
int rear; //队列尾
struct task_struct *wait_tasks[QUE_LEN]; //等待队列PCB数组
};
typedef struct semaphore_queue sem_queue; //定义信号量结构体别名
struct semaphore_t
{
int value;
int occupied;
char name[16]; //信号量名称,最长15个字符,因为最后一个是结束符
sem_queue wait_queue; //等待队列,每个信号量都维护一个队列
};
typedef struct semaphore_t sem_t; //定义信号量别名
(2)kernel/sys_sem.c实现
点击查看代码
#define __LIBRARY__
#include <unistd.h> //里面定义一些结构体和变量,信号有关的结构体定义也在里面,并且通过符号__LIBRARY__来确定是否包含
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/segment.h>
#include <asm/system.h>
#define SEM_COUNT 32 //信号量的数量
sem_t semaphores[SEM_COUNT]; //定义信号量数组
/*定义信号量队列操作的相关函数*/
void init_queue(sem_queue *q)
{
//初始化队列,头指针等于尾指针,队列空
q->front = q->rear = 0;
}
int is_empty(sem_queue *q)
{
//判断队列是否为空
return q->front == q->rear ? 1 : 0;
}
int is_full(sem_queue *q)
{
//判断队列是否满
return (q->rear + 1) % QUE_LEN == q->front ? 1 : 0;
}
struct task_struct* get_task(sem_queue* q)
{
//获取队列头第一个任务
if(is_empty(q))
{
printk("Queue is empty!\n");
return NULL;
}
struct task_struct *tmp = q->wait_tasks[q->front];
q->front = (q->front + 1) % QUE_LEN;
return tmp;
}
int insert_task(struct task_struct *p, sem_queue* q)
{
//将任务插入尾部
if(is_full(q))
{
printk("Queue is full!\n");
return -1;
}
q->wait_tasks[q->rear] = p;
q->rear = (q->rear + 1) % QUE_LEN;
return 1;
}
int sem_location(const char *name)
{
//检查信号量是否已经打开,如果打开了返回信号量位置
int i;
for(i = 0; i < SEM_COUNT; i++)
{
if(strcmp(name, semaphores[i].name) == 0 && semaphores[i].occupied == 1)
{
return i;
}
}
return -1;
}
sem_t* sys_sem_open(const char *name, unsigned int value)
{
//打开并创建信号量
char tmp[16];
char c;
int i;
//逐个字符从用户空间复制到内核空间
for(i = 0; i < 16; i++)
{
c = get_fs_byte(name + i); //从用户空间复制字符到内核空间
tmp[i] = c;
if(c == '\0') break;
}
if(c >= 16)
{
printk("Semaphore name is too long!Limit 16!\n");
return NULL;
}
if((i = sem_location(tmp)) != -1) //已经存在该信号量,直接返回信号量
{
return &semaphores[i];
}
//该信号量还没有被创建,创建
for(i = 0; i < SEM_COUNT; i++)
{
if(!semaphores[i].occupied)
{
strcpy(semaphores[i].name, tmp);
semaphores[i].occupied = 1;
semaphores[i].value = value;
init_queue(&(semaphores[i].wait_queue));
printk("semaphore %s creates successful!\n", tmp);
return &semaphores[i];
}
}
printk("Numbers of semaphores are limited %d\n", SEM_COUNT);
return NULL;
}
int sys_sem_wait(sem_t* sem)
{
//p原子操作,使用中断实现
cli();
sem->value--;
if(sem->value < 0)
{
current->state = TASK_UNINTERRUPTIBLE;
insert_task(current, &(sem->wait_queue));
//printk("go sleep\n");
schedule(); //调度别的进程,此时别的进程会从其内核栈中弹出EFLAG,里面的中断标志位是打开的,所以中断会在别进程里重新打开
}
sti(); //因为某些事件进程重新恢复运行后会执行到这里,需要重新打开中断
return 0;
}
int sys_sem_post(sem_t *sem)
{
//V操作
cli();
struct task_struct *p;
sem->value++;
if(sem->value <= 0)
{
p = get_task(&(sem->wait_queue));
if(p != NULL)
{
(*p).state = TASK_RUNNING;
//printk("voke a process\n");
//sti();
//schedule(); //激活后没有马上调度的话会导致队列中只有一个进程被唤醒
//return 0;
}
}
sti();
return 0;
}
int sys_sem_unlink(const char *name)
{
//释放信号量
char tmp[16];
char c;
int i;
for(i = 0; i < 16; i++)
{
c = get_fs_byte(name + i);
tmp[i] = c;
if(c == '\0') break;
}
if(c >= 16)
{
printk("Semaphore name is limited in 16 char!\n");
return -1;
}
int ret = sem_location(tmp);
if(ret != -1)
{
semaphores[ret].value = 0;
strcpy(semaphores[ret].name, '\0');
semaphores[ret].occupied = 0;
return 0;
}
return -1;
}
(3)/kernel/system_call.s代码修改如下
点击查看代码
nr_system_calls = 76
(4)/include/linux/sys.h
点击查看代码
#define __LIBRARY__
#include <unistd.h>
extern int sys_setup();
extern int sys_exit();
extern int sys_fork();
extern int sys_read();
extern int sys_write();
extern int sys_open();
extern int sys_close();
extern int sys_waitpid();
extern int sys_creat();
extern int sys_link();
extern int sys_unlink();
extern int sys_execve();
extern int sys_chdir();
extern int sys_time();
extern int sys_mknod();
extern int sys_chmod();
extern int sys_chown();
extern int sys_break();
extern int sys_stat();
extern int sys_lseek();
extern int sys_getpid();
extern int sys_mount();
extern int sys_umount();
extern int sys_setuid();
extern int sys_getuid();
extern int sys_stime();
extern int sys_ptrace();
extern int sys_alarm();
extern int sys_fstat();
extern int sys_pause();
extern int sys_utime();
extern int sys_stty();
extern int sys_gtty();
extern int sys_access();
extern int sys_nice();
extern int sys_ftime();
extern int sys_sync();
extern int sys_kill();
extern int sys_rename();
extern int sys_mkdir();
extern int sys_rmdir();
extern int sys_dup();
extern int sys_pipe();
extern int sys_times();
extern int sys_prof();
extern int sys_brk();
extern int sys_setgid();
extern int sys_getgid();
extern int sys_signal();
extern int sys_geteuid();
extern int sys_getegid();
extern int sys_acct();
extern int sys_phys();
extern int sys_lock();
extern int sys_ioctl();
extern int sys_fcntl();
extern int sys_mpx();
extern int sys_setpgid();
extern int sys_ulimit();
extern int sys_uname();
extern int sys_umask();
extern int sys_chroot();
extern int sys_ustat();
extern int sys_dup2();
extern int sys_getppid();
extern int sys_getpgrp();
extern int sys_setsid();
extern int sys_sigaction();
extern int sys_sgetmask();
extern int sys_ssetmask();
extern int sys_setreuid();
extern int sys_setregid();
extern sem_t * sys_sem_open();
extern int sys_sem_wait();
extern int sys_sem_post();
extern int sys_sem_unlink();
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid, sys_sem_open, sys_sem_wait, sys_sem_post, sys_sem_unlink};
(5)应用层的usr/include/unistd.h和内核中的一样,只是二者所处的路径不同,并且应用层的是应用程序使用,而内核中的unistd.h是内核代码编译时使用
(6)在linux中验证生产者消费者程序时,需要进行一定的修改,并且我们自己写的信号量系统调用与标准的也存在不同,修改后的pc.c如下:
点击查看代码
#define __LIBRARY__
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#define N 5 //消费者进程个数
#define BUFFER_SIZE 10
#define BUFFER_FILE "buffer.txt"
#define MAX_NUM 20
int item = 0;
int write_pos = 0;
int fd;
sem_t *empty_slots;
sem_t *full_slots;
sem_t *mutex;
_syscall2(sem_t*, sem_open, const char*, name, unsigned int, value);
_syscall1(int, sem_wait, sem_t*, sem);
_syscall1(int, sem_post, sem_t*, sem);
_syscall1(int, sem_unlink, const char*, name);
void Consumer(int fd)
{
int read_pos;
int read_buf;
while(1)
{
sem_wait(full_slots);
sem_wait(mutex);
lseek(fd, BUFFER_SIZE * sizeof(int), SEEK_SET);
read(fd, (char *)&read_pos, sizeof(int));
lseek(fd, read_pos * sizeof(int), SEEK_SET);
read(fd, (char *)&read_buf, sizeof(int));
printf("%d: %d\n", getpid(), read_buf);
read_pos++;
read_pos %= 10;
lseek(fd, BUFFER_SIZE * sizeof(int), SEEK_SET);
write(fd, (char *)&read_pos, sizeof(int));
sem_post(mutex);
sem_post(empty_slots);
}
exit(0);
}
void Producer(int fd)
{
int i;
for(i = 0; i < MAX_NUM; i++)
{
sem_wait(empty_slots);
sem_wait(mutex);
lseek(fd, write_pos * sizeof(int), SEEK_SET);
write(fd, (char *)&i, sizeof(int));
write_pos++;
write_pos %= 10;
sem_post(mutex);
sem_post(full_slots);
}
exit(0);
}
int main()
{
int out;
int i;
mutex = sem_open("mutex", 1);
if(mutex == NULL)
{
printf("errno: %s\n",strerror(errno));
return -1;
}
empty_slots = sem_open("empty", BUFFER_SIZE);
if(empty_slots == NULL)
{
printf("errno: %s\n",strerror(errno));
return -1;
}
full_slots = sem_open("full", 0);
if(full_slots == NULL)
{
printf("errno: %s\n",strerror(errno));
return -1;
}
fd = open(BUFFER_FILE, O_RDWR | O_CREAT | O_TRUNC, 0644);
if(fd < 0)
{
perror("file create failed: ");
}
else
{
printf("%s create successfully.\n", BUFFER_FILE);
}
out = 0;
lseek(fd,BUFFER_SIZE * sizeof(int), SEEK_SET);
write(fd, (char *)&out, sizeof(int));
if(fork() == 0)
{
Producer(fd);
return 0;
}
if(fork() == 0)
{
Consumer(fd);
return 0;
}
if(fork() == 0)
{
Consumer(fd);
return 0;
}
if(fork() == 0)
{
Consumer(fd);
return 0;
}
if(fork() == 0)
{
Consumer(fd);
return 0;
}
if(fork() == 0)
{
Consumer(fd);
return 0;
}
wait(NULL);
if(sem_unlink("empty") == 0)
{
printf("empty unlinked successfully\n");
}
else
{
perror("empty Failed to unlink semaphore");
}
if(sem_unlink("full") == 0)
{
printf("full unlinked successfully\n");
}
else
{
perror("full Failed to unlink semaphore");
}
if(sem_unlink("mutex") == 0)
{
printf("mutex unlinked successfully\n");
}
else
{
perror("mutex Failed to unlink semaphore");
}
if(close(fd) == 0)
{
printf("close %s file successfully.\n", BUFFER_FILE);
}
else
{
perror("close file failed.");
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通