信号量
1.资源竞争
- 资源竞争 : 当多个进程同时访问共享资源时,会产生资源竞争,最终最导致数据混乱
- 临界资源 : 不允许同时有多个进程访问的资源,包括硬件资源(CPU、内存、存储器以及其他外围设备)与软件资源(共享代码段、共享数据结构)
- 临界区 : 访问临界资源代码
2.同步与互斥
- 互斥 : 同一时刻只有一个进程访问临界资源
- 同步 : 在互斥的基础上增加了进程对临界资源的访问顺序
- 进程主要的同步与互斥手段是信号量
3.信号量简介
信号量的起源来自于信号灯
-
信号量: 由内核维护的整数,其值被限制为大于或等于0
-
信号量可以执行如下操作:
1.将信号量设置成一个具体的值2.在信号量当前值的基础上加上一个数值3.在信号量当前值的基础上减上一个数值4.等待信号量的值为 01.
-
一般信号量分为二值信号量与计数信号量
1.二值信号量:一般指的是信号量的值为1,可以理解为只对应一个资源
2.计数信号量:一般指的是值大于等于2 ,可以理解为对应多个资源
- 在 Linux 系统中查询信号量使用ipcs -s
4.创建信号量
创建信号量集合调用semget函数
1.函数原型
int semget(key_t key, int nsems, int semflg);
2.函数头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
2.函数功能
创建一个信号量集合
3.函数参数
key:由ftok()函数生成
nsems:信号量的数量
semflg:信号量集合的标志IPC_CREAT:创建标志IPC_EXCL:与IPC_CREAT标志一起使用,如果信号量集合存在就报错权限标志
4.函数返回值
成功:返回信号量集合的id
失败:-1,并设置errno
5.初始化信号量
初始化信号量调用 semctl 函数
函数头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数原型
int semctl(int semid, int semnum, int cmd, …);
函数功能
信号量集合控制函数,根据cmd决定当前函数的功能
函数参数
semid:信号量集合的id
semnum:信号量的编号,信号量的编号从0开始
cmd:命令控制字
SETVAL: 设置信号量的值(Set the semaphore value (semval) to arg.val for
the semnum-th semaphore of the set, updating also the sem_ctime member of the
semid_ds structure associated with the set.)
[将信号量值(semval)设置为该集合的第semnum个信号量的arg.val,同时更新与该集合相关联
的semid_ds结构的sem_ctime成员。]
GETVAL: 获取信号量的值
SETALL: Set the semval values for all semaphores of the set using
arg.array,
......
...:后面是属于可变参数列表,根据不同的命令有不同的参数
函数返回值
成功:根据不同的命令有不同的返回值,可以查看帮助文档关于 RETURN 的说明
GETNCNT the value of semncnt
GETPID the value of sempid
GETVAL the value of semval
GETZCNT the value of semzcnt.
All other cmd values return 0 on success.
失败:返回-1,并设置 errno
使用命令时需要使用 union semun 共用体,具体定义如下:
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) */
};
The semid_ds data structure is defined in <sys/sem.h> as follows:
struct semid_ds {struct ipc_perm sem_perm; /* Ownership and permissions */time_t sem_otime; /* Last semop time */time_t sem_ctime; /* Creation time/time of lastmodification via semctl() */unsigned long sem_nsems; /* No. of semaphores in set */
};
6.信号量的操作
信号量可以进行以下操作:
- 对信号量可以进行以下操作:
- 对信号量的值减1
- 等待信号量的值为0
操作信号量调用semop函数
1.函数头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
2.函数原型
int semop(int semid, struct sembuf *sops, size_t nsops);
3.函数功能
信号量操作函数,用于占用信号量、释放信号量、设置信号量等待
4.函数参数
semid:信号量集合id
sops:信号量操作结构体指针
nsops:操作的信号量的数量
5.函数返回值
成功:返回 0
失败:返回-1,并设置errno
struct sembuf 结构体
sements of this structure are of type struct sembuf, containing the following
members:unsigned short sem_num; /* semaphore number */short sem_op; /* semaphore operation */short sem_flg; /* operation flags */
说明:
sem_num:信号量编号,从0开始
sem_op:信号量操作-1:占用资源+1:释放资源0:等待资源
sem_flg:信号量操作标志IPC_NOWAIT:非阻塞,在信号量的值为0时,会立即返回SEM_UNDO:在进程终止时,会自动释放信号量
7.信号量集合删除:
信号量集合调用 semctl 函数,设置命令为 IPC_RMID
int semctl(int semid, int semnum, int cmd, ...);
使用方式:
int ret=semctl(semid,0,IPC_RMID,NULL);
8.信号量数组测试案列(使用信号量解决父子进程对终端的竞争)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#define PATHNAME "./"
#define PATHNUMBER 68
union semun{unsigned short *array;
};
//创造信号量
int sem_create(int nsems){key_t key = ftok(PATHNAME,PATHNUMBER);if(key == -1){perror("key");exit(EXIT_FAILURE);}int semid = semget(key,nsems,0644 | IPC_CREAT);if(semid == -1){perror("semid");exit(EXIT_FAILURE);} union semun sem;unsigned short arr[1] = { 1 };sem.array = arr;int figure = semctl(semid,0,SETALL,sem);if(figure == 1){perror("figure");exit(EXIT_FAILURE);}return semid;
}
//占据信号量数组
void sem_occupy(int semid,int operation_id){struct sembuf sops;sops.sem_num = operation_id;sops.sem_op = -1;//占据资源sops.sem_flg = SEM_UNDO;//进程终止时,会释放资源int ret = semop(semid,&sops,1);if(ret == -1){perror("ret");exit(EXIT_FAILURE);}
}
//释放信号量数组
void sem_release(int semid,int operation_id){struct sembuf sops;sops.sem_num = operation_id;sops.sem_op = 1;//释放资源sops.sem_flg = SEM_UNDO;//进程终止时,会释放资源int ret = semop(semid,&sops,1);if(ret == -1){perror("ret");exit(EXIT_FAILURE);} }
int main(){int semid = sem_create(1);int pid = fork();if(pid == -1){perror("pid");exit(EXIT_FAILURE);}else if(pid > 0){while(1){sem_occupy(semid,0);printf("parent start\n");printf("parent end\n");sleep(2);sem_release(semid,0);}}else if(pid == 0){while(1){sem_occupy(semid,0);printf("son start\n");printf("son end\n");sleep(2);sem_release(semid,0); }}return 0;
}
9.信号量之间的同步
实现思路:
-
通过创建一个信号量集合,包含 2个信号量,一个信号量编号为0**(SEM_CONTROL_P)控制父进程的运行与暂停,一个信号量编号为1(SEM_CONTROL_C)**控制子进程的运行与暂停
-
控制逻辑
-
信号初始化
- SEM_CONTROL_P : 初始化为 0
- SEM_CONTROL_C : 初始化为 1
-
子进程
占用 SEM_CONTROL_C ,此时子进程阻塞
当父进程释放 SEM_CONTROL_C 时, 子进程开始工作 ,释放 SEM_CONTROL_P
循环占用 SEM_CONTROL_C,由于之前已经占用,此时进入子进程阻塞,等待父进程释放SEM_CONTROL_C
- 父进程
占用 SEM_CONTROL_P,此时父进程正常运行,运行程序
释放 SEM_CONTROL_C,占用 SME_CONTROL_P,此时父进程阻塞,子进程继续执行
当子进程输出 B 之后,释放 SEM_CONTROL_P,父进程继续执行,运行进程
父进程释放 SEM_CONTROL_P 循环结束
父子进程的同步的案例(c语言) :
任务:创建一个子进程,将当前系统时间按照 1.<2024-2-5 14:30:29> 格式写入到文件中
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#define PATHNAME "./"
#define PATHNUMBER 67
#define PATH "./log.txt"
union semun{unsigned short *array;
};
#define SEM_CONTROL_P 0
#define SEM_CONTROL_S 1
//创造信号量
int sem_create(int nsems){key_t key = ftok(PATHNAME,PATHNUMBER);if(key == -1){perror("key");exit(EXIT_FAILURE);}int semid = semget(key,nsems,0644 | IPC_CREAT);if(semid == -1){perror("semid");exit(EXIT_FAILURE);} union semun sem;unsigned short arr[2] = { 1,0 };sem.array = arr;int figure = semctl(semid,0,SETALL,sem);if(figure == -1){perror("figure");exit(EXIT_FAILURE);}return semid;
}
//占据信号量数组
void sem_occupy(int semid,int operation_id){struct sembuf sops;sops.sem_num = operation_id;sops.sem_op = -1;//占据资源sops.sem_flg = SEM_UNDO;//进程终止时,会释放资源int ret = semop(semid,&sops,1);if(ret == -1){perror("ret");exit(EXIT_FAILURE);}
}
//释放信号量数组
void sem_release(int semid,int operation_id){struct sembuf sops;sops.sem_num = operation_id;sops.sem_op = 1;//释放资源sops.sem_flg = SEM_UNDO;//进程终止时,会释放资源int ret = semop(semid,&sops,1);if(ret == -1){perror("ret");exit(EXIT_FAILURE);} }
int main(){int semid = sem_create(2);int pid = fork();FILE* pf = fopen(PATH,"a+");if(pf == NULL){perror("pf:");exit(EXIT_FAILURE);}if(pid == -1){perror("pid");exit(EXIT_FAILURE);}else if(pid > 0){while(1){sem_occupy(semid,SEM_CONTROL_P);fprintf(pf,"%s","1.<");fflush(pf);sem_release(semid,SEM_CONTROL_S);sem_occupy(semid,SEM_CONTROL_P);fprintf(pf,"%s\n",">");fflush(pf);printf("输入文件成功\n");sleep(2);sem_release(semid,SEM_CONTROL_P);}}else if(pid == 0){while(1){sem_occupy(semid,SEM_CONTROL_S);time_t timep;struct tm *p;time(&timep);p=gmtime(&timep);char time[128] = { 0 }; sprintf(time,"%d-%d-%d %d:%d:%d",1900 +p->tm_year,1+p->tm_mon,p->tm_mday,8 + p->tm_hour,p->tm_min,p->tm_sec);printf("%s\n",time);fprintf(pf,"%s",time);fflush(pf);sem_release(semid,SEM_CONTROL_P);} }return 0;
}