信号量 (Semaphores)
信号量是一种用于进程间或线程间同步的机制。它可以限制多个进程或线程对共享资源的并发访问,确保资源被安全使用。信号量的核心思想是通过计数来控制访问,计数值表示当前可以访问资源的可用数量。
- 计数器: 信号量的核心是一个整数计数器。
- 当计数器大于0时,表示有资源可用。
- 当计数器等于0时,表示资源不可用,需要等待。
- 操作:
- 等待(Wait/P): 也叫P操作,尝试减小信号量的计数器(-1)。如果计数器为0,进程或线程将被阻塞,直到计数器大于0。
- 释放(Signal/V): 也叫V操作,增加信号量的计数器(+1)。如果有进程或线程在等待信号量,它们会被唤醒。
POSIX 信号量
POSIX信号量是基于POSIX标准实现的,可以用于进程间或线程间的同步。一般用于描述一种共享资源的状态,Linux系统把POSIX信号量分为两种:POSIX匿名信号量(和具名)。Linux系统也把POSIX信号量存储在根文件系统的/dev/shm目录下(被任意有权限的进程访问)。
以下是一些常用在进程的POSIX具名信号量函数:
函数及其用途
-
sem_open
-
用途: 创建或打开一个命名信号量。
-
原型:
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
-
**@param **
-
name信号量名;
-
oflag用来指定打开信号量的行为(例如,是否创建它,是否非阻塞等);
-
mode指定了信号量的访问权限;
-
value 是信号量的初始值,通常用于计数信号量。
示例:
/*在/usr/include/x86_64-linux-gnu/bits/semaphore.h文件中的sem_t*/ typedef union { char __size[__SIZEOF_SEM_T]; long int __align; } sem_t;
sem_t *sem = sem_open("/my_sem", O_CREAT | O_EXCL, 0644, 1); if (sem == SEM_FAILED) { if (errno == EEXIST) { sem_t *sem = sem_open("/my_sem", 0 , 0644, 1); } else { fprintf(stderr, "sem_open error,error:%d, %s\n", errno, strerror(errno)); exit(EXIT_FAILURE); } }
-
-
sem_wait
-
用途: 等待信号量(P操作),计数器减1。如果计数器为0,则阻塞。
-
原型:
int sem_wait(sem_t *sem);
-
-
sem_post
-
用途: 释放信号量(V操作),计数器加1。如果有等待的进程或线程,则唤醒其中一个。
-
原型:
int sem_post(sem_t *sem);
-
-
sem_close
-
用途: 关闭信号量的引用,但不删除信号量。
-
原型:
int sem_close(sem_t *sem);
-
-
sem_unlink
-
用途: 删除信号量,释放其名字。只有当所有对信号量的引用都被关闭后,信号量才会被真正删除。
-
原型:
int sem_unlink(const char *name);
-
System V 信号量
System V信号量是早期UNIX系统的实现方式,主要用于进程间同步。它们提供了更复杂的功能,但使用起来也相对复杂。
函数及其用途
-
semget
-
用途: 创建或获取一个信号量集。
-
原型:
int semget(key_t key, int nsems, int semflg);
-
-
semop
-
用途: 执行一个或多个信号量操作(P操作和V操作)。
-
原型:
int semop(int semid, struct sembuf *sops, unsigned nsops);
-
-
semctl
-
用途: 控制信号量集,如获取和设置信号量值,删除信号量集等。
-
原型:
int semctl(int semid, int semnum, int cmd, ...);
-
POSIX 信号量与 System V 信号量的区别
- 命名机制:
- POSIX 信号量: 支持命名信号量,允许进程间共享。
- System V 信号量: 使用键值(key)来标识信号量集,需要通过
ftok
函数生成键值。
- 用法范围:
- POSIX 信号量: 可以用于进程间和线程间同步。
- System V 信号量: 主要用于进程间同步。
示例:使用 POSIX 信号量
设计两个程序A和B,程序A和程序B需要创建一个共享内存,然后要求程序A把自己的PID写入到共享内存中,当程序A写入完成后,程序B从共享内存中读取程序A的PID并输出到终端,要求双方使用POSIX具名信号量实现同步。
//程序A把自己的PID写入到共享内存
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <semaphore.h>
#include <fcntl.h>
#include <unistd.h>
int shm_id;
sem_t *sem;
const char *sem_name = "/my_sem";
int oflag = O_CREAT | O_EXCL;
/* 在UNIX和类UNIX操作系统中,S_IRUSR、S_IWUSR、S_IRGRP、S_IWGRP、S_IROTH 和 S_IWOTH 是权限位宏。
* 用于设置文件或目录的访问权限,这些宏定义在 <sys/stat.h> 头文件中。
* S_IRUSR (Read by owner): 所有者有读权限;
* S_IWUSR (Write by owner): 所有者有写权限;
* S_IRGRP (Read by group): 同组用户有读权限;
* S_IWGRP (Write by group): 同组用户有写权限;
* S_IROTH (Read by others): 其他用户有读权限;
* S_IWOTH (Write by others): 其他用户有写权限; */
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
unsigned int value = 0;
int main() {
//创建共享内存段
shm_id = shmget(ftok(".", 1), 4, IPC_CREAT | IPC_EXCL | 0644);
if (shm_id == -1) //判断创建是否出错
{
if (errno == EEXIST) {
shm_id = shmget(ftok(".", 1), 4, 0644);
} else {
fprintf(stderr, "shmget error,error:%d, %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
}
//映射虚拟内存空间
int *buffer = (int *) shmat(shm_id, NULL, 0);
if (buffer == (void *) -1) //判断创建是否出错
{
fprintf(stderr, "shmat error,error:%d,%s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
//创建信号量
sem = sem_open(sem_name, oflag, mode, value);
if (sem == SEM_FAILED) {
if (errno == EEXIST) {
sem = sem_open(sem_name, 0, mode, value);
} else {
fprintf(stderr, "sem_open error,error:%d, %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
}
//临界区
*buffer = getpid();
//V操作
sem_post(sem);
//关闭信号量
if (sem_close(sem) < 0) {
fprintf(stderr, "sem_close error,error:%d, %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
//程序B从共享内存中读取程序A的PID并输出到终端
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <semaphore.h>
#include <fcntl.h>
#include <unistd.h>
int shm_id;
sem_t *sem;
int main() {
//打开共享内存段
shm_id = shmget(ftok(".", 1), 4, 0644);
if (shm_id == -1) //判断创建是否出错
{
fprintf(stderr, "shmget error,error:%d, %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
//映射虚拟内存空间
int *buffer = (int *) shmat(shm_id, NULL, 0);
if (buffer == (void *) -1) //判断创建是否出错
{
fprintf(stderr, "shmat error,error:%d,%s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
//打开信号量
sem = sem_open("/my_sem", 0);
if (sem == SEM_FAILED) {
fprintf(stderr, "sem_open error,error:%d, %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
//P操作
sem_wait(sem);
//临界区
printf("Procedure A pid = %d\n", *buffer);
//关闭信号量
if (sem_close(sem) < 0) {
fprintf(stderr, "sem_close error,error:%d, %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
示例:使用 System V 信号量
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
int main() {
key_t key = ftok("semfile", 1); // 生成唯一键值
int semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666); // 创建信号量集
// 初始化信号量值
semctl(semid, 0, SETVAL, 1);
struct sembuf sop;
// P操作
sop.sem_num = 0;
sop.sem_op = -1;
sop.sem_flg = 0;
semop(semid, &sop, 1);
printf("In critical section\n");
sleep(2); // 模拟临界区操作
// V操作
sop.sem_op = 1;
semop(semid, &sop, 1);
printf("Out of critical section\n");
// 删除信号量集
semctl(semid, 0, IPC_RMID);
return 0;
}
标签:同步,int,errno,信号量,互斥,error,sem,include
From: https://www.cnblogs.com/Mr--Song/p/18225269