目录
一、什么是信号量
二、PV操作概念
三、信号灯
四、有名信号灯
五、无名信号灯
一、什么是信号量
线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。
注意:
信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件semaphore.h中。
二、PV操作概念
生产者和消费者场景
P(S) 含义如下:
if (信号量的值大于0) {
申请资源的任务继续运行;
信号量的值减一;
} else {
申请资源的任务阻塞;
}
V(S) 含义如下:
信号量的值加一;
if (有任务在等待资源) {
唤醒等待的任务,让其继续运行
}
信号灯P操作:
int sem_wait(sem_t *sem);
获取资源,如果信号量为0,表示这时没有相应资源空闲,那么调用线程就将挂起,直到有空闲资源可以获取
该函数用于以原子操作的方式将信号量的值减1。原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。
等待信号量,如果信号量的值大于0,将信号量的值减1,立即返回。如果信号量的值为0,则线程阻塞。相当于P操作。成功返回0,失败返回-1。sem指向的对象是由sem_init调用初始化的信号量。
信号灯V操作:
int sem_post(sem_t *sem);
释放资源,如果没有线程阻塞在该sem上,表示没有线程等待该资源,这时该函数就对信号量的值进行增1操作,表示同类资源多增加了一个。如果至少有一个线程阻塞在该sem上,表示有线程等待资源,信号量为0,这时该函数保持信号量为0不变,并使某个阻塞在该sem上的线程从sem_wait函数中返回
该函数用于以原子操作的方式将信号量的值加1。释放信号量,让信号量的值加1。相当于V操作。与sem_wait一样,sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.
注意:编译posix信号灯需要加pthread动态库。
三、信号灯分类
三种信号灯:
Posix 有名信号灯
Posix 无名信号灯 (linux只支持线程同步)
System V 信号灯
Posix 有名信号灯和无名信号灯使用:
四、有名信号灯
有名信号灯打开:
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
参数:
name:name是给信号灯起的名字
oflag:打开方式,常用O_CREAT
mode:文件权限。常用0666
value:信号量值。二元信号灯值为1,普通表示资源数目
信号灯文件位置:/dev/shm
有名信号灯关闭
int sem_close(sem_t *sem);
有名信号灯的删除
int sem_unlink(const char* name);
shmat函数:
shmat 函数的使用-CSDN博客https://blog.csdn.net/wangshiqueque/article/details/9628633
shmat 函数的使用
shmat()是用来允许本进程访问一块共享内存的函数,与shmget()函数共同使用。
shmat的原型是:void *shmat(int shmid,const void *shmaddr,int shmflg);
它需要如下3个参数:
第一个是参数是 shmid 是shmget 返回的标识符,
第二个参数 三种情况
1.如果shmaddr 是NULL,系统将自动选择一个合适的地址!
2.如果shmaddr 不是NULL 并且没有指定SHM_RND则此段连接到addr所指定的地址上。
3.如果shmaddr非0 并且指定了SHM_RND 则此段连接到shmaddr -(shmaddr mod SHMLAB)所表示的地址上。这里解释一下SHM_RND命令,它的意思是取整,而SHMLAB的意思是低边界地址的倍数,它总是2的乘方,该
算式是将地址向下取最近一个SHMLAB的倍数。 除非只计划在一种硬件上运行应用程序(在现在是不太可
能的),否则不用指定共享段所连接到的地址。
所以一般指定shmaddr为0,以便由内核选择地址。
第三个参数如果在flag中指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写的方式连接此
段。 shmat返回值是该段所连接的实际地址,如果出错返回 -1。
shmget函数:
linux中shmget函数_linux shm_get-CSDN博客https://blog.csdn.net/qq_33573235/article/details/79169624
int shmget(key_t key, size_t size, int flag);
key: 标识符的规则
size:共享存储段的字节数
flag:读写的权限
返回值:成功返回共享存储的id,失败返回-1
size是要建立共享内存的长度。
所有的内存分配操作都是以页为单位的。所以如果一段进程只申请一块只有一个字节的内存,内存也会分配整整一页(在i386机器中一页的缺省大小PACE_SIZE=4096字节)这样,新创建的共享内存的大小实际上是从size这个参数调整而来的页面大小。
即如果size为1至4096,则实际申请到的共享内存大小为4K(一页);4097到8192,则实际申请到的共享内存大小为8K(两页),依此类推。
使用System V共享内存创建了一个共享内存段,并使用两个信号量进行同步。它允许从标准输入写入共享内存段。
a. key_t key:使用 ftok() 函数定义一个System V IPC的键。该函数基于提供的路径和标识符生成一个键。在这里,它根据当前目录(".")和标识符100生成一个键。
b. shmid:使用 shmget() 函数创建或访问一个共享内存段。如果共享内存段不存在,则此函数会分配一个大小为500字节的共享内存段,权限为读写(0666)。用于访问共享内存的键是由 ftok() 生成的。
c. char* shmaddr:使用 shmat() 函数将共享内存段附加到调用进程的地址空间中。第二个参数是 NULL,表示系统选择一个适当的地址来存储段。第三个参数是 0,表示读写访问。
d. sem_t* sem_r, *sem_w:声明用于读写操作的信号量指针。
e. sem_open():使用 sem_open() 函数打开或创建两个信号量(mysem_r 和 mysem_w)。这些信号量用于同步。使用 O_CREAT 标志创建信号量(如果它们不存在)。0666 是权限模式,mysem_r 的初始值设置为 0,mysem_w 的初始值设置为 1。
f. sem_wait(sem_w)`:等待写信号量。该信号量确保只有一个进程可以同时向共享内存写入数据。
`printf(">")`:打印提示符。
`fgets(shmaddr,500,stdin)`:从标准输入(`stdin`)读取输入,并将其写入共享内存段。
`sem_post(sem_r)`:发出读信号量以指示数据可供读取。
注意:
函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。
sem_wait函数也是一个原子操作,它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。也就是说,如果你对一个值为2的信号量调用sem_wait(),线程将会继续执行,介信号量的值将减到1。如果对一个值为0的信号量调用sem_wait(),这个函数就会地等待直到有其它线程增加了这个值使它不再是0为止。如果有两个线程都在sem_wait()中等待同一个信号量变成非零值,那么当它被第三个线程增加一个“1”时,等待线程中只有一个能够对信号量做减法并继续执行,另一个还将处于等待状态。
函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
有名信号灯:
sem_w.c
#include<stdio.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<semaphore.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/shm.h>
int main()
{
key_t key;
key = ftok(".",100);
if(key < 0)
{
perror("ftok");
return 0;
}
int shmid;
shmid = shmget(key,500,0666|IPC_CREAT);
if(shmid < 0)
{
perror("shmget");
return 0;
}
char* shmaddr;
shmaddr = shmat(shmid,NULL,0);
sem_t* sem_r,*sem_w;
sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
while(1)
{
sem_wait(sem_w);
printf(">");
fgets(shmaddr,500,stdin);
sem_post(sem_r);
}
return 0;
}
注意:
在程序中,两个信号量 mysem_r
和 mysem_w
被创建并初始化:
-
mysem_r
是一个用于读取操作的信号量,它的初始值被设为0
。这意味着,当程序开始时,读取操作将会被阻塞,直到有数据可供读取。 -
mysem_w
是一个用于写入操作的信号量,它的初始值被设为1
。这意味着,当程序开始时,写入操作可以立即执行,因为初始值为1
的信号量允许写入操作进行。 -
当
mysem_w
的值为1
时,写入信号量为"可用"状态,即表示没有其他进程正在写入数据,因此允许当前进程进行写入操作。这样可以避免程序一开始就被阻塞,从而使得写入操作能够立即执行。在程序的执行过程中,当一个进程(比如本程序中的循环)进行写入操作时,会将mysem_w
的值减少到0
,表示写入信号量已被占用,其他进程在这时候无法进行写入操作。只有在写入操作完成并释放了信号量后,其他进程才能进行写入操作。因此,初始将
mysem_w
的值设为1
是为了确保程序开始时写入操作可以立即执行,而不会被阻塞。
因此,这种设置确保在程序启动时,写入操作可以立即进行,而读取操作需要等待直到有数据可供读取。
sem_r.c
#include<stdio.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<semaphore.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/shm.h>
int main()
{
key_t key;
key = ftok(".",100);
if(key < 0)
{
perror("ftok");
return 0;
}
int shmid;
shmid = shmget(key,500,0666|IPC_CREAT);
if(shmid < 0)
{
perror("shmget");
return 0;
}
char* shmaddr;
shmaddr = shmat(shmid,NULL,0);
sem_t* sem_r,*sem_w;
sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
while(1)
{
sem_wait(sem_r);
printf("%s\n",shmaddr);
sem_post(sem_w);
}
return 0;
}
运行结果:
test_r.c
#include<stdio.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<semaphore.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<signal.h>
void handle(int sig)
{
sem_unlink("mysem_w");
exit(0);
}
int main()
{
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
key_t key;
key = ftok(".",100);
if(key < 0)
{
perror("ftok");
return 0;
}
int shmid;
shmid = shmget(key,500,0666|IPC_CREAT);
if(shmid < 0)
{
perror("shmget");
return 0;
}
char* shmaddr;
shmaddr = shmat(shmid,NULL,0);
sem_t* sem_r,*sem_w;
sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
while(1)
{
sem_wait(sem_r);
printf("%s\n",shmaddr);
sem_post(sem_w);
}
return 0;
}
int sem_unlink(const char *name):从系统中删除有名信号量
test_w.c
#include<stdio.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<semaphore.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<signal.h>
void handle(int sig)
{
sem_unlink("mysem_r");
exit(0);
}
int main()
{
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
key_t key;
key = ftok(".",100);
if(key < 0)
{
perror("ftok");
return 0;
}
int shmid;
shmid = shmget(key,500,0666|IPC_CREAT);
if(shmid < 0)
{
perror("shmget");
return 0;
}
char* shmaddr;
shmaddr = shmat(shmid,NULL,0);
sem_t* sem_r,*sem_w;
sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
while(1)
{
sem_wait(sem_w);
printf(">");
fgets(shmaddr,500,stdin);
sem_post(sem_r);
}
return 0;
}
运行结果:
五、无名信号灯
无名信号灯初始化
int sem_init(sem_t *sem, int shared, unsigned int value);
参数:
sem:需要初始化的信号灯变量
shared: shared指定为0,表示信号量只能由初始化这个信号量的进程使用,不能在进程间使用,linux 不支持进程间同步。
Value:信号量的值
无名信号灯销毁
int sem_destroy(sem_t* sem);
无名信号灯:
该程序包含一个主进程和一个读取进程。主进程负责从标准输入中读取用户输入,并将其写入共享内存中,读取进程则从共享内存中读取数据并输出到标准输出。
1.信号处理函数,用于在程序中断时销毁信号量destroysem
2.读取进程函数readmem
sem_wait(&sem_r); // 等待写入信号量
printf("%s\n",shmaddr); // 读取共享内存内容并打印
sem_post(&sem_w); // 发送写入信号量
3.设置信号处理函数sigaction
4.生成共享内存ftok
5.创建共享内存shmget
6.将共享内存连接到当前进程的地址空间shmat
7.初始化信号量sem_init
8.创建读取进程pthread_create
9.读取数据并输出到标准输出
sem_wait(&sem_w); // 等待读取信号量
printf(">");
fgets(shmaddr,500,stdin); // 从标准输入读取数据到共享内存
sem_post(&sem_r); // 发送读取信号量
具体代码:
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <pthread.h>
sem_t sem_r,sem_w;
char *shmaddr;
void destroysem(int sig){
sem_destroy(&sem_r);
sem_destroy(&sem_w);
exit(0);
}
void *readmem(void *arg){
while(1){
sem_wait(&sem_r);
printf("%s\n",shmaddr);
sem_post(&sem_w);
}
}
int main(){
key_t key;
int shmid;
struct sigaction act;
act.sa_handler = destroysem;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
key = ftok(".",100);
if(key<0){
perror("ftok");
return 0;
}
shmid = shmget(key,500,0666|IPC_CREAT);
if(shmid<0){
perror("shmget");
return 0;
}
shmaddr = shmat(shmid,NULL,0);
sem_init(&sem_r,0,0);
sem_init(&sem_w,0,1);
pthread_t tid;
pthread_create(&tid,NULL,readmem,NULL);
while(1){
sem_wait(&sem_w);
printf(">");
fgets(shmaddr,500,stdin);
sem_post(&sem_r);
}
}
这个程序的主要功能是从标准输入中读取用户输入,并将其写入共享内存,然后另一个进程从共享内存中读取数据并打印到标准输出。
运行结果: