并发与竞争(四)信号量
创始人
2024-03-25 03:42:24
0

文章目录

  • 信号量概念
    • 概念
    • 信号的工作方式
    • 信号量的描述
    • 信号量的API函数
    • 信号量的注意事项
  • 写代码
    • 简单实现:信号量实现驱动只能被一个程序调用
    • 完成代码

信号量概念

概念

两个特点:

  • 信号量会引起调用者的休眠
  • 在持有锁的时间比较长的情况下,使用自旋锁是不合适的;得用信号量。

 自旋锁是通过“原地等待”的方式来处理并发与竞争的,所以被保护的临界区不能太长,以免造成对CPU资源的浪费。但是有些情况我们必不可免的要长时间对一些资源进行保护。这时候就可以使用信号量了。

什么是信号量呢?
 举个例子,现在有一个电话亭,里面只有一个公共电话。某天A去打电话,恰好过了一会B也来了要打电话。但是此时A在打电话,所以B就只能等待A打完电话才可以打。如果是自旋锁的,B就要一直等待着A打完。但是A的事情很重要,需要打很长时间电话。这时候自旋锁就不合适了。那A是不是就可以告诉B,你先去休息一会儿,等我打完了告诉你,你再来打电话。这个就是信号量。信号量会引起调用者睡眠,所以信号量也叫睡眠锁。

信号的工作方式

 信号量的本质是一个全局变量。信号量的值可以根据实际情况来自行设置(取值范围大于等于0),当有线程来访问资源时,信号量执行“减一”操作,访问完以后,再执行“加一”操作。

 比如一个屋子有5把钥匙,这5把钥匙就是信号量的值,也就是说5个人可以进到这个屋子(允许多个线程同时访问共享资源)。当某个人想进屋子的时候,就要先拿一把钥匙,此时信号量的值“减一”。直到这5把钥匙全部拿走以后,这个屋子别人就进不去了。如果有人从屋子里面出来,还回去一把钥匙,此时信号量的值“加一”那就又可以进去一个人了。

信号量的描述

Linux内核使用结构体semaphore来表示信号量,定义在semaphore.h文件中,如下所示:

struct semaphore {raw_spinlock_t    lock;unsigned int      count;struct list_head  wait_list;
};

信号量的API函数

函数描述
DEFINE_SEMAPHORE(name)定义信号量,并设置信号量的值为1
void sema_init(struct semaphore *sem, int val)初始化信号量sem并设置信号的值为val
void down(struct semaphore *sem)获取信号量,不能被信号打断,如Ctrl+C
int down_interruptible(struct semaphore *sem)获取信号量,能被信号打断,如Ctrl+C
void up(struct semaphore *sem)释放信号量
int down_trylock(struct semaphore *sem)尝试获取信号量,如果获取到信号量就返回0,获取不到就返回非0

信号量的注意事项

  1. 信号量的值不能小于0
  2. 访问共享资源时,信号量执行“减一”操作,访问完毕后再执行“加一”操作
  3. 当信号量的值为0时,想访问共享资源的线程必须等待,知道信号量大于0时,等待的线程才可以访问
  4. 因为信号量会引起休眠,所以中断里面不能用信号量
  5. 共享资源持有时间比较长,一般用信号量而不用自旋锁
  6. 在同时使用信号量和自旋锁的时候,要先获取信号量,再使用自旋锁。因为信号量会导致睡眠。

写代码

简单实现:信号量实现驱动只能被一个程序调用

核心代码

static struct semaphore semlock;static void module_init(void)
{...sema_init(&semlock, 1);//设置值为1,就是说只能允许一个程序调用:一把钥匙...
}int misc_open(struct inode *inode,struct file *file)
{
#if 0down(&semlock);
#endifif(down_interruptible(&semlock)) // 可以被中断打断的信号量,减一{return -EINTR;}printk("hello misc_open\n ");return 0;
}int misc_release(struct inode *inode,struct file *file)
{up(&semlock); // 信号量加一,释放掉后就可以被其他程序打开了printk("hello misc_relaease bye bye \n ");return 0;
}

完成代码

模块代码:

#include          //初始化头文件
#include        //最基本的文件,支持动态添加和卸载模块。
#include    //包含了miscdevice结构的定义及相关的操作函数。
#include            //文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)
#include       //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include            //包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include 		//驱动要写入内核,与内核相关的头文件#include 
#include  #define GPIO_DR 0xfdd60000     //LED物理地址,通过查看原理图得知
unsigned int *vir_gpio_dr;     //存放映射完的虚拟地址的首地址static struct semaphore semlock;int misc_open(struct inode *inode,struct file *file)
{
#if 0down(&semlock);
#endifif(down_interruptible(&semlock)) // 可以被中断打断的信号量,减一{return -EINTR;}printk("hello misc_open\n ");return 0;
}int misc_release(struct inode *inode,struct file *file)
{up(&semlock); // 信号量加一,释放掉后就可以被其他程序打开了printk("hello misc_relaease bye bye \n ");return 0;
}ssize_t misc_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{printk("misc_read\n ");return 0;
}ssize_t misc_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{	/*应用程序传入数据到内核空间,然后控制蜂鸣器的逻辑,在此添加*/// kbuf保存的是从应用层读取到的数据char kbuf[64] = {0};// copy_from_user 从应用层传递数据给内核层if(copy_from_user(kbuf,ubuf,size)!= 0) {// copy_from_user 传递失败打印printk("copy_from_user error \n ");return -1;}//打印传递进内核的数据//printk("kbuf is %d\n ",kbuf[0]); if(kbuf[0]==1) //传入数据为1 ,LED亮{*vir_gpio_dr = 0x80008000; }else if(kbuf[0]==0) //传入数据为0,LED灭*vir_gpio_dr = 0x80000000;return 0;
}//文件操作集
struct file_operations misc_fops={.owner = THIS_MODULE,.open = misc_open,.release = misc_release,.read = misc_read,.write = misc_write,
};
//miscdevice结构体
struct miscdevice  misc_dev = {.minor = MISC_DYNAMIC_MINOR,.name = "hello_misc",.fops = &misc_fops,
};
static int misc_init(void)
{int ret;sema_init(&semlock, 1);//设置值为1,就是说只能允许一个程序调用:一把钥匙//注册杂项设备ret = misc_register(&misc_dev);if(ret<0){printk("misc registe is error \n");}printk("misc registe is succeed \n");//将物理地址转化为虚拟地址vir_gpio_dr = ioremap(GPIO_DR,4);if(vir_gpio_dr == NULL){printk("GPIO_DR ioremap is error \n");return EBUSY;}printk("GPIO_DR ioremap is ok \n");	return 0;
}
static void misc_exit(void){//卸载杂项设备misc_deregister(&misc_dev);iounmap(vir_gpio_dr);printk(" misc gooodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");

测试APP

#include 
#include 
#include 
#include 
#include int main(int argc,char *argv[])
{int fd;char buf[64] = {0};//定义buf缓存char val[1];char cnt = 0;//打开设备节点fd = open("/dev/hello_misc",O_RDWR);if(fd < 0){//打开设备节点失败perror("open error \n"); return fd;}printf("open ok!\n");sleep(10);close(fd);printf("close ok!\n");return 0;
}

测试:

[root@RK356X:/opt]# insmod led.ko
[24800.610524] misc registe is succeed
[root@RK356X:/opt]# [24800.611512] GPIO_DR ioremap is ok[root@RK356X:/opt]#
[root@RK356X:/opt]# cp app.armelf app2.armelf
[root@RK356X:/opt]# ./app.armelf &
[root@RK356X:/opt]# open ok![root@RK356X:/opt]# ./app2.armelf
close ok!
[24834.366689] hello misc_open
[24834.366689]
[24844.open ok!
367459] hello misc_relaease bye bye
[24844.367459]
l248c4l4o.s3e6 7o5k7!2] helo misc_open
[24844.367572]
[1]+  Done                       ./app.armelf

效果是没有问题:

  • app后台运行,打开设备节点成功
  • app2前台运行,无法打开设备节点,然后就进入睡眠,直到app释放,app2才打开成功。

下面就是多复制了一个,然后app,app2,app3就排着队打开设备节点了。

[root@RK356X:/opt]# cp app.armelf app3.armelf
[root@RK356X:/opt]# ./app.armelf &
[root@RK356X:/opt]# [24854.369391] hello misc_relaease bye bye
[24854.369391]
open ok!
[root@RK356X:/opt]# ./app2.armelf &
[root@RK356X:/opt]# ./app.aclose ok!
[25146.299718] hello misc_open
[25146.299718]
[25156.3011open ok!
86] hello misc_relaease bye bye
[root@RK356X:/opt]# ./app3.armelf6lose ok![2515.301322] hello misc_open
[25156.301322]
[25166.303open ok!
492] hello misc_relaease bye bye
[25166.303492]
5lose ok![2166.303620] hello misc_open
[25166.303620]
[2]+  Done                       ./app2.armelf
[1]+  Done                       ./app.armelf

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...