Linux信号灯

目录

一、什么是信号量

二、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博客icon-default.png?t=N7T8https://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博客icon-default.png?t=N7T8https://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_rmysem_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);
	}

}

        这个程序的主要功能是从标准输入中读取用户输入,并将其写入共享内存,然后另一个进程从共享内存中读取数据并打印到标准输出。

运行结果: 

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值