134.Linux并发与竞争-信号量

本文详细介绍了Linux内核中的信号量机制,作为一种用于控制对共享资源访问的计数器,信号量允许线程在无法获取资源时进入休眠状态,提高处理器效率。文章通过实例展示了如何在驱动开发中使用信号量,包括定义、初始化、获取和释放信号量的API,并给出了LED驱动的示例代码,展示了如何限制10秒内只能打开一次驱动。同时,讨论了信号量与自旋锁在不同场景下的适用性。
摘要由CSDN通过智能技术生成

1. 前言

开发板:正点原子阿尔法Linux开发板

2.简介

信号量为一个计数器,用来控制对共享资源的访问,假如某一个停车场只能容纳50辆车,只能停车数量就是一个信号量值,实际的停车数量就是信号量的值,有车开进去信号量加1,开出来信号量减1。

​ 相当于自旋锁来说,信号量可以使线程进入休眠状态,如果这个线程获取不到信号量不会和自旋锁一样一直自旋,而是进入休眠状态,让出CPU,提高处理器的运行效率,但是信号量使线程进入休眠之后会切换线程,加大了系统开销。

2.1 信号量的特点

  • 信号量可以使等待信号量的线程进入休眠状态,适用于资源占用比较久的场景
  • 信号量不能使用到中断中,因为信号量会导致线程休眠,而中断不能休眠
  • 如果共享资源持有的时间比较短,不适合用信号量,使用自旋锁可能比较合适,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。

3.信号量相关API

在Linux内核中,信号量使用semaphore结构体表示

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

3.1 基本API

DEFINE_SEAMPHORE(name) //定义一个信号量,并且设置信号量的值为 1。
void sema_init(struct semaphore *sem, int val)// 初始化信号量 sem,设置信号量值为val。
void down(struct semaphore *sem)//获取信号量,因为会导致休眠,因此不能在中断中使用。
void up(struct semaphore *sem) //释放信号量

尝试获取信号量,获取到信号量就返回 0。如果不能就返回非 0,并且不会进入休眠。

int down_trylock(struct semaphore *sem);

获取信号量,和 down 类似,使用 down 进入休眠状态的线程不能被信号打断。使用此
函数进入休眠以后可以被信号打断。

int down_interruptible(struct semaphore *sem)

3.2 伪代码

信号量的使用为:

struct semaphore sem ;//定义一个信号量
sema_init(&sem,1);    //初始化信号量的值
down(&sem);           //获取信号量
up(&sem);             //释放信号量

4.代码示例

功能:实现功能,10s只能打开一次驱动

驱动文件:

my_led_driver.c

#include "my_led_driver_reg.h" 

DEFINE_SEMAPHORE(sem);
int flags;

/*初始化LED*/
static void memory_map(void)
{
	IMX6U_CLK_CCGR1 			     = ioremap(CCM_CCGR1,4);
	SW_MUX_CTL_PAD_GPIO1_IO03 		 = ioremap(SW_MUX_CTL_PAD_GPIO1_IO03_BASE,4);
	IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = ioremap(SW_PAD_CTL_PAD_GPIO1_IO03_BASE,4);
	IMX6UL_GPIO1_DR                  = ioremap(GPIO1_DR,4);
	IMX6UL_GPIO1_GDIR                = ioremap(GPIO1_GDIR,4);                   
}

static void free_memory_map(void)
{
	iounmap(IMX6U_CLK_CCGR1);
	iounmap(SW_MUX_CTL_PAD_GPIO1_IO03);	
	iounmap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03);
	iounmap(IMX6UL_GPIO1_DR);
	iounmap(IMX6UL_GPIO1_GDIR);
}

static void gpio1_clk_enable(void)
{
	u32 iClkVal;

	/*读取之前的clk值*/
	iClkVal = readl(IMX6U_CLK_CCGR1);

	/*清空27:26位,清除之前的设置*/
	iClkVal &= ~(3 << 26);

	/*设置新的值*/
	iClkVal |= (3 << 26);
	writel(iClkVal,IMX6U_CLK_CCGR1);

}

static void set_gpio1_mux_fun(void)
{
	u32 iVal;

	/*设置gpio1的复用功能,初始值都是0x0000000*/
	
	writel(5,SW_MUX_CTL_PAD_GPIO1_IO03);

	/* 3、配置 GPIO1_IO03 的 IO 属性
	 *bit 16     : 0 HYS 关闭
	 *bit [15:14]: 00 默认下拉
     *bit [13]   : 0 kepper 功能
     *bit [12]   : 1 pull/keeper 使能
	 *bit [11]   : 1 打开开路输出
     *bit [7:6]  : 10 速度 100Mhz
     *bit [5:3]  : 111 R0/7 驱动能力
     *bit [0]    : 1 高转换率
     *初始值为0x0000000,直接写
     */
	writel(0x000018b9,IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03);

	/*设置gpio1_io03为输出功能*/
	iVal = readl(IMX6UL_GPIO1_GDIR);
	iVal &=(1 << 3);
	iVal |=(1 << 3);
	writel(iVal,IMX6UL_GPIO1_GDIR);

	/*默认关闭LED,根据原理图可知低电平触发*/
	iVal = readl(IMX6UL_GPIO1_DR);
	iVal |=(1 << 3);
	writel(iVal,IMX6UL_GPIO1_DR);
		
}

void led_switch(int iLedSta)
{
	u32 iVal;
	iVal = readl(IMX6UL_GPIO1_DR);
	
	switch(iLedSta)
	{
		case LED_ON:
		{
			iVal &= ~(1 << 3);
			break;
		}
		case LED_OFF:
		{
			iVal |= (1 << 3);
			break;
		}
	default:
		{
			iVal &=~(1 << 3);
			break;
		}
	}
	
	writel(iVal,IMX6UL_GPIO1_DR);
}

//echo
static ssize_t led_enable_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t len)
{
	bool iLedEnable;
	u32 iRet;

	iRet  = strtobool(buf, &iLedEnable);
	if(iRet < 0)
	{
		debug("strtobool failed\n");
		return iRet;
	}

	debug("++klz led enable is=%d\n",iLedEnable);
	led_switch(iLedEnable);
		
    return len;
}

//声明led_enable文件节点
static DEVICE_ATTR(led_enable, S_IWUSR, NULL,led_enable_store);

static const struct attribute *atk_imx6ul_led_sysfs_attrs[] = {
	&dev_attr_led_enable.attr,
	NULL,
};


static int imx6ull_led_open(struct inode *inode, struct file *file)
{
	//file->private_data = &led_device;
	
	down(&sem);
	
	if(flags)
	{
		up(&sem);
		printk(KERN_INFO"driver is running,open failed\n");
		return -EINVAL;
	}
	else
	{
		flags++;
	}
	
	up(&sem);
	file->private_data = &led_device;
	
	return 0;
}


static int imx6ull_led_release(struct inode *inode, struct file *file)
{
	file->private_data = &led_device;
	down(&sem);
	if(flags)
		flags--;
	
	up(&sem);
	
	return 0;
}

static ssize_t imx6ull_led_read(struct file *file, char __user *buf, size_t cnt, loff_t * loff)
{
	return 0;
}

static ssize_t imx6ull_led_write (struct file *file, const char __user *buf, size_t cnt, loff_t *loff)
{
	u32 iRet;
	unsigned char cALedbuf[1];
	unsigned int ledSta;

	iRet = copy_from_user(cALedbuf,buf,cnt);
	if(iRet < 0)
	{
		debug("copy from user failed\n");
		return -EINVAL;
	}
	
	if(iRet < 0)
	{
		debug("imx6ull write failed\n");
		return -EINVAL;
	}

	printk(KERN_INFO"%s,cALedbuf[0]=%d\n",__FUNCTION__,cALedbuf[0]);

    ledSta = cALedbuf[0] - 48;
	led_switch(ledSta);

	return 0;

}


static struct file_operations led_device_fops = {
	.owner   = THIS_MODULE,
	.read    = imx6ull_led_read,
	.write   = imx6ull_led_write,
	.open    = imx6ull_led_open,
	.release = imx6ull_led_release,
};

static int __init led_driver_init(void)
{
	u32 iRet;

	/*1.硬件初始化:初始化内存映射,初始化硬件*/
	memory_map();
	gpio1_clk_enable();
	set_gpio1_mux_fun();

	/*2.1 之前定义了主设备号*/
	if(led_device.major)
	{
		/*选择次设备号*/
		led_device.devid = MKDEV(led_device.major,0);
		/*注册设备号*/
		iRet = register_chrdev_region(led_device.devid, DEVICE_CNT, DEVICE_NAME);
		if(iRet < 0)
		{
			debug("register_chrdev_region failed\n");
			return iRet;
		}
	}else
	{
		/*向内核申请主次设备号,DEVICE_NAME体现在/proc/devices*/
		alloc_chrdev_region(&led_device.devid, 0, DEVICE_CNT,  DEVICE_NAME); /* 申请设备号 */
		led_device.major = MAJOR(led_device.devid); /* 获取分配号的主设备号 */
		led_device.minor = MINOR(led_device.devid); /* 获取分配号的次设备号 */
	}

	led_device.cdev.owner = THIS_MODULE;
	cdev_init(&led_device.cdev,&led_device_fops);
	/*自动创建设备结点,在/dev目录下体现*/
	iRet = cdev_add(&led_device.cdev,led_device.devid,DEVICE_CNT);
	if(iRet < 0)
	{
		debug("cdev_add device failed\n");
		goto fail_cdev_add;
	}

	led_device.class = class_create(THIS_MODULE,DEVICE_NAME);
	if(IS_ERR(led_device.class))
	{
		debug("class creat failed\n");
		goto fail_class_create;
	}

	/*生成dev/DEVICE_NAME文件*/
	led_device.device = device_create(led_device.class,NULL,led_device.devid,NULL,DEVICE_NAME);
	if(IS_ERR(led_device.device))
	{
		debug("device class failed\n");
		goto fail_device_create;
	}

	/*创建led_enable结点,直接通过系统调用来操作驱动*/
	iRet = sysfs_create_files(&led_device.device->kobj,atk_imx6ul_led_sysfs_attrs);
	if(iRet)
	{
		debug("failed to create sys files\n");
		return -EINVAL;
	}
	
	debug("my led dirver init sucess\n");

	return 0;
fail_cdev_add:
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	return iRet;
fail_class_create:
	cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	return -1;
fail_device_create:
	cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	class_destroy(led_device.class);
	return -1;
	
}

static void __exit led_driver_exit(void)
{
	free_memory_map();
	cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	/*依赖于class所以先删除*/
	device_destroy(led_device.class, led_device.devid);
	class_destroy(led_device.class);

}

module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_AUTHOR("klz <1255713178@qq.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("led driver of atk imx6ull");

my_led_driver_reg.h

#ifndef MY_LED_DRIVER_REG_H
#define MY_LED_DRIVER_REG_H


#include <linux/types.h>	    /*设备号所在头文件*/
#include <linux/module.h>       /*内核模块声明的相关函数*/
#include <linux/init.h>			/*module_init和module_exit*/
#include <linux/kernel.h>		/*内核的各种函数*/
#include <asm/io.h>             /*readl.ioremap函数*/
#include <linux/kernel.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <asm/mach/map.h>
#include <linux/ide.h>
#include <linux/semaphore.h>

#define _DEBUG_ 
#ifdef  _DEBUG_
	#define  debug(fmt, args...)  	printk("[file = %s][fun = %s]["fmt"]\n",__FILE__,__FUNCTION__,##args)
#else
	#define  debug(fmt, args...)  	do{}while(0)
#endif


#define DEVICE_NAME "led_driver"
#define DEVICE_CNT   1
#define LED_ON		 1
#define LED_OFF      0

/*
*编写步骤:
*   0.编写驱动框架
*	1.使能GPIO的时钟
*	2.初始化 GPIO,比如输出功能、上拉、速度
*	3.设置复用
*	4.输出高低电平
*/

#define CCM_CCGR_BASE    				(0x20C4000)
#define CCM_CCGR1        				(CCM_CCGR_BASE + 0x6C)               /*时钟控制器*/      

#define SW_MUX_BASE   					(0x20E0000)
#define SW_MUX_CTL_PAD_GPIO1_IO03_BASE  (SW_MUX_BASE + 0x68)  /*io的复用控制器*/
#define SW_PAD_CTL_PAD_GPIO1_IO03_BASE  (SW_MUX_BASE + 0x2F4) /*io的速度设置,驱动能力设置,压摆率设置*/

#define GPIO_BASE       (0x209C000)
#define GPIO1_DR        (GPIO_BASE + 0x0)
#define GPIO1_GDIR      (GPIO_BASE + 0x4)
#define GPIO1_PSR       (GPIO_BASE + 0x8)
#define GPIO1_ICR1      (GPIO_BASE + 0xC)
#define GPIO1_ICR2      (GPIO_BASE + 0x10)
#define GPIO1_IMR       (GPIO_BASE + 0x14)
#define GPIO1_ISR       (GPIO_BASE + 0x18)
#define GPIO1_EDGE_SEL  (GPIO_BASE + 0x1C)


void __iomem *IMX6U_CLK_CCGR1;					        /*时钟控制寄存器 bit27:bit 26 = 1:1  gpio1_enable*/
void __iomem *SW_MUX_CTL_PAD_GPIO1_IO03;		        /*io的复用功能 bit4=1 bit3:bit0=0101*/
void __iomem *IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03;	        /*速度设置、驱动能力设置、压摆率设置*/
void __iomem *IMX6UL_GPIO1_DR;						    /*数据寄存器,置0置1*/
void __iomem *IMX6UL_GPIO1_GDIR;                        /*GPIO输入输出位设置*/

void __iomem *IMX6UL_GPIO1_PSR;                        /*只读位,读取相应的位就可获得gpio的状态,高电平还是低电平*/
void __iomem *IMX6UL_GPIO1_ICR1;                       /*中断触发位,前16位以什么方式来进行触发*/
void __iomem *IMX6UL_GPIO1_ICR2;                       /*中断触发,后16位以什么方式进行中断触发*/
void __iomem *IMX6UL_GPIO1_IMR;                        /*中断使能位*/
void __iomem *IMX6UL_GPIO1_ISR;                        /*可读取哪一个中断被触发*/
void __iomem *IMX6UL_GPIO1_EDGE_SEL;                   /*如果此位被设置,那么会双边沿触发中断*/

struct led_device_t
{
	dev_t devid;		    /*设备号*/
	struct cdev cdev;       /*cdev*/
	struct class *class;	    /*类*/
	struct device *device;	/*设备*/
	int    major;			/*主设备号*/
	int    minor;			/*次设备号*/
};

struct led_device_t led_device;

#endif

Makefile

KERNELDIR := /home/klz/linux/linux-4.1.15

CURRENT_PATH := $(shell pwd)
obj-m := my_led_driver.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
copy:
	cp *.ko /home/klz/linux/nfs/rootfs/lib/modules/4.1.15
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

make之后将生成的再执行make copy命令将生成的ko文件拷贝到根文件目录下

测试程序:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
    /*判断输入的命令是否合法*/
    if(argc != 3)
    {
        printf(" commend error ! \n");
        return -1;
    }

	char* filename = argv[1];
	
    /*打开文件*/   
    int fd = open(filename, O_RDWR);
    if(fd < 0)
    {
		printf("fd=%d.open file : /dev/rgb_led failed !!!\n",fd);
		return -1;
	}


    /*写入命令*/
    int error = write(fd,argv[2],sizeof(argv[2]));
    if(error < 0)
    {
        printf("write file error! \n");
        close(fd);
        /*判断是否关闭成功*/
    }


    sleep(10);  //休眠10秒

    /*关闭文件*/
    error = close(fd);
    if(error < 0)
    {
        printf("close file error! \n");
    }
    return 0;
}

生成可执行文件,且拷贝到板子上

arm-linux-gnueabihf-gcc app.c -o ledApp
sudo cp ledApp /home/klz/linux/nfs/rootfs/lib/modules/4.1.15/
chmod +x ledApp

5.测试

加载驱动

depmod
modprobe my_led_driver,ko

执行应用程序

./ledApp /dev/led_driver 1 &
10s之内运行下面指令打开驱动失败
./ledApp /dev/led_driver 0
    
驱动层打印:driver is running,open failed
应用层打印:fd=-1.open file : /dev/rgb_led failed !!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值