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);
	}

}

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

运行结果: 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/467386.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

搜索二叉树迭代和递归的两种*简单*实现方式

判断搜索二叉树 概念 一棵树所有结点的左节点小于父节点&#xff0c;右节点大于父节点&#xff0c;则为搜索二叉树。 迭代方法 中序遍历二叉树&#xff0c;如果总是升序则是搜索二叉树。如果存在降序&#xff0c;那肯定不是搜索二叉树。 Coding checkTreeOrder()方法 boo…

js判断对象是否有某个属性

前端判断后端接口是否返回某个字段的时候 <script>var obj { name: "John", age: 30 };console.log(obj.hasOwnProperty("name")); // 输出 trueconsole.log(obj.hasOwnProperty("email")); // 输出 falselet obj11 { name: "Joh…

DNF的概念和操作命令

yum是linux系统中基于rpm包管理的一种软件管理工具。 在dnf.conf文件中&#xff0c;我们可以配置某个网络服务器位软件源仓库。配置的方法&#xff0c;就是用vim编辑/etc/dnf/dnf.conf这个文件。

数字化转型导师坚鹏:人工智能在金融机构数字化转型中的应用

人工智能在金融机构数字化转型中的应用 课程背景&#xff1a; 金融机构数字化转型离不开人工智能&#xff0c;在金融机构数字化转型中&#xff0c;人工智能起到至关重要的作用&#xff0c;很多机构存在以下问题&#xff1a; 不清楚人工智能产业对我们有什么影响&#xff1f;…

Netty学习——源码篇2 客户端Bootstrap(一) 备份

1 Channel简介 在Netty中&#xff0c;Channel相当于一个Socket的抽象&#xff0c;它为用户提供了关于Socket状态&#xff08;是连接还是断开&#xff09;以及对Socket的读写等操作。每当Netty建立了一个连接&#xff0c;都创建一个与其对应的Channel实例。 除了TCP&#xff0c;…

考研数学|跟武忠祥,刷什么习题集效果最好?

选择听哪位老师的课程并不是硬性规定。我个人觉得&#xff0c;关键在于根据自己的学习需求和情况来选择合适的学习方式。比如如果听武忠祥老师的课程可能更适合你&#xff0c;你可以选择武忠祥老师&#xff1b;而如果你希望通过大量的题目练习来提高解题能力&#xff0c;那么选…

IntelliJ IDEA 设置运行时环境变量

背景 博主要测试langchain4j&#xff0c;运行时需要OPENAI_BASE_URL和OPENAI_API_KEY这两个环境变量的值。 临时设置 Run -> Edit Configurations -> Edit Environmental Variables 永久设置 在系统环境变量中设置&#xff0c;教程无数。 注意&#xff1a;windows在…

进程地址空间的进一步认识

进程地址空间 地址空间的大小取决于系统的架构和操作系统的实现。在32位系统中&#xff0c;地址空间大小为2的32次方&#xff08;约为4GB&#xff09;。而在64位系统中&#xff0c;地址空间大小为2的64次方。 进程地址空间的划分使得不同的数据和代码可以在不同的区域中进行管…

IM项目题

消息协议 消息的可靠性 前言 IM系统的可靠性指的是端到端的可靠性&#xff0c;并不是tcp的可靠性&#xff0c;它是指客户端A&#xff0c;客户端B以及服务端三端通信之间的可靠性&#xff0c;并不是客户端A到服务端这么一个上行消息的可靠&#xff0c;这个tcp就可以保证了&#…

洛谷 P1378 油滴扩展

本道题可以理解成一个平面直角坐标系&#xff0c;在坐标系上标出整个矩形和油滴的坐标&#xff0c;计算两个油滴的面积和直径&#xff0c;判断点是否在圆内&#xff08;点与圆的位置关系&#xff09;&#xff0c;利用使用坐标求两点间距离的公式取解。 代码如下&#xff1a; …

定位及解决OOM

一、定义 内存溢出&#xff1a;OutOfMemoryError&#xff0c;是指因内存不够&#xff0c;导致操作新对象没有剩余空间。会导致频繁fullgc出现STW从而导致性能下降。 内存泄漏&#xff1a;指用malloc或new申请了一块内存&#xff0c;但是没有通过free或delete将内存释放&#…

一维坐标的移动(bfs)

在一个长度为n的坐标轴上&#xff0c;小S想从A点移动B点。 他的移动规则如下&#xff1a; 向前一步&#xff0c;坐标增加1。 向后一步&#xff0c;坐标减少1。 跳跃一步&#xff0c;使得坐标乘2。 小S不能移动到坐标小于0或大于n的位置。 小S想知道从A点移动到B点的最少步数是多…

四.排序(冒泡/选择)

目录 11-排序介绍 常见排序算法: 12-冒泡排序介绍 代码要求: 思路: 13-冒泡排序 代码: 14-选择排序 简单写法: 好的写法: 11-排序介绍 排序&#xff1a;将一组“无序”的记录序列调整为“有序”的记录序列。 列表排序&#xff1a;将无序列表变为有序列表 输入&#…

LeetCode 2312.卖木头块:动态规划(DP)

【LetMeFly】2312.卖木头块&#xff1a;动态规划(DP) 力扣题目链接&#xff1a;https://leetcode.cn/problems/selling-pieces-of-wood/ 给你两个整数 m 和 n &#xff0c;分别表示一块矩形木块的高和宽。同时给你一个二维整数数组 prices &#xff0c;其中 prices[i] [hi, …

SCI一区 | Matlab实现RIME-TCN-BiGRU-Attention霜冰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测

SCI一区 | Matlab实现RIME-TCN-BiGRU-Attention霜冰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现RIME-TCN-BiGRU-Attention霜冰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型描述程…

面试经典150题(114-118)

leetcode 150道题 计划花两个月时候刷完之未完成后转&#xff0c;今天完成了5道(114-118)150 gap 了一周&#xff0c;以后就不记录时间了。。 114.(70. 爬楼梯) 题目描述&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不…

【CTF笔记】 CTF web方向笔记分享 免费 附预览图

个人不怎么记东西&#xff0c;笔记不多&#xff0c;师傅们凑合看… 百度网盘&#xff1a;https://pan.baidu.com/s/1PspihUX28Y_AOQZPurHqKA 麻烦各位师傅帮忙填写一下问卷&#xff0c;提取码在问卷填写结束后显示~ 【https://www.wjx.cn/vm/mBBTTKm.aspx# 】 &#xff08;…

6大赚钱平台大揭秘,正规靠谱,电脑手机均可操作增收

找到一个真正靠谱的赚钱平台&#xff0c;无疑是你开启创收之旅的绝佳起点&#xff01;接下来&#xff0c;我将为你提供一些建议&#xff0c;帮助你在这浩瀚的互联网世界中&#xff0c;稳稳地迈出赚取第一桶金的第一步。 参与调查问卷&#xff1a;像Swagbucks和YouGov这样的调查…

信号量——生产消费者模型

前文 在这一篇博客&#xff08;信号量博客&#xff09;中我曾经提及过信号量的知识&#xff0c;而当对信号量进行提炼总结时&#xff0c;大致是以下三点&#xff1a; 1. 信号量本质是一个计数器&#xff08;代表资源的数量&#xff09; 2. 申请信号量本质就是对资源的一种预定机…

AI大模型额外学习一:斯坦福AI西部世界小镇笔记(包括部署和源码分析)

文章目录 一、简单介绍1&#xff09;项目代码介绍2&#xff09;重新播放模拟3&#xff09;适当修改分叉模拟 二、部署斯坦福小镇Demo1&#xff09;准备工作2&#xff09;解决遇到的bug3&#xff09;启动服务器和前端 三、源码剖析1&#xff09;主题顺序 github链接 一、简单介…