要求:使用信号量实现两个进程轮流写入data.txt文件末尾,写入间隔为1秒,每个进程写入10次,内容随意
基本概念
- 信号量是一种计数器,用来控制对多个进程共享的资源所进行的访问。常常被用做一个锁机制,在某个进程正在对特定的资源进行操作时,信号量可以防止另一个进程去访问它。生产者和消费者的模型是信号量的典型使用
- 信号量分为两种:system
代码编写思路
- 根据不同的项目需求选择不同的信号量(二进制,计数型......),以当前题目为例,该需求只有两个进程,因此适合使用二进制信号量(0/1)来实现两个进程的交替进行。
步骤一:创建信号量
- 使用semget()系统调用创建两个信号量,初始化一个为1(允许执行)另一个为0(等待状态)
函数: int semget(key_t key,int nsems,int semflg)
参数:key是使用ftok创建的键值nsems是创建信号量的数量,在本例中创建的数量为 2semflg是打开信号量的方式,有两种方式:IPC_CREAT/IPC_EXCL
步骤二:进程同步逻辑
- 进程1:等待自己的信号量(值为1时继续执行),执行写入操作,然后将另一个信号量设置为1
- 进程2同理
- 主要操作包括:1.
P操作:将信号量值减1,如果值小于0则阻塞进程
2.V操作:将信号量值加1,如果值大于等于0则唤醒等待进程 - P、V操作是通过向已经建立好的信号量(semget()函数),发送命令来完成
步骤三:循环执行
- 每个进程循环10次,每次执行完写入操作后使用
sleep(1)实现1秒间隔
步骤四:清理资源
- 程序结束时,使用
semctl()或sem_close()清理信号量资源
关键系统调用
#include <sys/sem.h> // System V信号量
#include <semaphore.h> // POSIX信号量
#include <sys/wait.h> //提供wait函数
#include <unistd.h> //包含Unix标准函数声明,如fork(),sleep()等
对于System V信号量:
semget(): 创建/获取信号量semop(): 操作信号量(P/V操作)semctl(): 控制信号量
对于POSIX信号量:
sem_open(): 创建或打开命名信号量sem_wait(): 等待(P操作)sem_post(): 发送信号(V操作)sem_close(): 关闭信号量
4. 示例逻辑结构
#include <stdio.h>
#include <sys/types.h> //提供key_t
#include <sys/ipc.h> //包含进程间的通信(IPC)相关函数
#include <sys/sem.h> //包含信号量相关函数
#include <unistd.h> //包含Unix标准函数声明,如fork()
#include <stdlib.h> //提供exit()函数
#include <sys/wait.h> //提供wait()函数
// 信号量初始化
sem_t *sem1 = sem_open("/sem1", O_CREAT, 0666, 1); // 进程1先执行
sem_t *sem2 = sem_open("/sem2", O_CREAT, 0666, 0); // 进程2等待
// 进程1代码
for(int i = 0; i < 10; i++) {
sem_wait(sem1); // 等待信号量1
// 写入文件操作
sleep(1); // 间隔1秒
sem_post(sem2); // 唤醒进程2
}
// 进程2代码
for(int i = 0; i < 10; i++) {
sem_wait(sem2); // 等待信号量2
// 写入文件操作
sleep(1); // 间隔1秒
sem_post(sem1); // 唤醒进程1
}
5.代码编写
/*
* 信号量的P、V操作是通过向已经建立好的信号量(通过segmet函数创建的),发送命令来完成的
* P操作函数:对指定的信号量执行等待操作(减1)
* V操作函数:对指定的信号量执行发送操作(加1)
*/
void sem_P(int sem_id,int sem_num)
{
struct sembuf sem_buf; //定义sembuf结构体变量,用于描述信号量
sem_buf.sem_num = sem_num; //设置要操作的信号量在集合中的索引
sem_buf.sem_op = -1; //设置操作类型:-1表示P操作(等待),资源减1
sem_buf.sem_flg = SEN_UNDO; //设置操作标志:SEM_UNDO表示进程终止时内核会撤销此操作
semop(sem_id,&sem_buf,1); //调用semop()函数执行信号量操作,操作信号量
}
//V操作同理
void sem_V(int sem_id,int sem_num)
{
struct sembuf sem_buf;
sem_buf.sem_num = sem_num;
sem_buf.sem_op = 1;
sem_buf.sem_flg = SEM_UNDO;
semop(sem_id,&sem_buf,1);
}
//信号量数据结构,用于semctl函数的第四个参数
union semun
{
int val; //整形变量,用于设置信号量的初始值
struct semid_ds *buf; //semid_ds结构体指针,用于IPC_STAT和IPC_SET命令
unsigned short *array; //数组类型,用于GETALL和SETALL
struct seminfo *__buf; //信号量内部结构,用于IPC_INFO命令
}
int sem_id;//全局变量,存储信号量集合的标识符
int main()
{
int ret; //创建一个返回结果变量
key_t key = ftok(".",'a');//创建一个键值
if(key < 0)
{
perror("create key failed ");
exit(1);
}
sem_id = semget(key,2,IPC_CREAT,0666);//创建信号量
if(sem_id < 0)
{
perror("create signal failed ");
exit(2);
}
union semun sem_union; //定义union semun联合体变量
sem_union.val = 1; //初始化第一个信号量(索引0)
ret = semctl(sem_id,0,SETVAL,sem_union);//使用semctl()函数设置信号量的值
//sem_id:信号量标识符
//0:表示要操作的信号量的值
//SETVAL:设置信号量
//sem_union:联合体参数,包含了要设置的值
if(ret < 0)
{
perror("process 1 semctl failed ");
exit(3);
}
//信号量0操作与其一致
sem_union.val = 0; //初始化第二个信号量
ret = semctl(sem_id,1,SETVAL,sem_union);
if(ret<0)
{
perror("process 2 semctl failed ");
exit(4);
}
pid_t pid = fork();//创建子进程
if(pid == -1)//创建子进程
{
perror("create process/fork failed");
exit(5);
}
else if(pid == 0)//子进程(进程2)
{
FILE *fp = fopen("data.txt","a");//子进程打开data.txt文件,以追加模式写入
//使用"a"模式,确保每次数据添加到结尾
if(fp == NULL)
{
perror("process 2 open file failed ");
exit(6);
}
for()
{
sem_P(sem_id,1); //调用sem_P函数,等待第二个信号量变为可用(等待进程1释放信号量)
fprintf(fp,"进程2学号:23125042023\n");//写入数据
fflush(fp);//刷新缓冲区,确保数据立即写入磁盘
printf("process 2 write data success \n");
sleep(1);//休眠1秒,实现写入间隔要求
sem_V(sem_id,0);//执行V操作,释放第一个信号量,通知进程1可以执行
}
fclose(fp); //关闭文件
exit(0); //子进程正常退出
}
else //父进程(进程1)
{
//操作和子进程相同,主要信号量的变化
FILE *fp = fopen("data.txt","a");
if(fp == NULL)
{
perror("process 1 open file failed ");
exit(7);
}
for(int i = 0; i < 10; i++)
{
sem_P(sem_id,0);
fprintf(fp,"进程1学号:23125042023\n");
fflush(fp);
printf("process 1 write data success \n");
sleep(1);
sem_V(sem_id,1);
}
fclose(fp);
wait(NULL);
}
ret = semctl(sem_id,IPC_RMID);//删除信号量集合,释放系统资源
if(ret < 0)
{
perror("delete signal failed ");
exit(8);
}
return 0;
}