一. 简介
上一篇文章简单介绍了 Linux内核提供的信号量的理论知识,文章地址如下:
Linux内核处理并发与竞争的一种方法:信号量-CSDN博客
本文通过代码来说明信号量如何使用。
这里我们来使用信号量实现了一次只能有一个应用程序访问 LED 灯,信号量可以导致休眠,因此,信号量保护的临界区没有运行时间限制,可以在驱动的 open 函数申请信号量,然后在 release 函数中释放信号量。
注意:信号量不能用在中断中,本实验我们不会在中断中使用信号量。
二. Linux内核的信号量代码举例
1. 创建工程
这里信号量代码在前面 8_spinlock工程代码的基础上进行更改。
(1)首先,创建 8_semaphore工程目录,将 8_spinlock工程拷贝到 8_semaphore工程目录下:
wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers$ mkdir 8_semaphore
wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers$ cp ./8_spinlock/* ./8_semaphore/ -rf
2)拷贝 8_spinlock实验目录中 .vscode文件及其下文件。
将8_spinlock工程代码下的 .vscode文件及其下文件拷贝 到 8_semaphore工程目录下(.vscode文件下的文件设置了驱动代码会调用到 Linux内核源码的路径):
wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers$ cp ./8_spinlock/.vscode/ ./8_semaphore/ -rf
(3) 清除8_spinlock工程目录下之前编译生成的目标文件:
wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers/8_semaphore$ make clean
(4)将 8_spinlock工程目录下的 spinlock.c文件名改为 semaphore.c。更改 Makefile文件中的目标文件名,更改为如下内容:
obj-m := semaphore.o
2. 向Led驱动代码中添加信号量
semaphore.c文件添加信号量代码后,如下所示:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#define LED_OFF 0
#define LED_ON 1
#define GPIOLED_NAME "gpioled"
#define GPIOLED_CNT 1
//Led设备结构体
struct gpio_led {
dev_t devid;
int major;
int minor;
struct cdev cdev;
struct class* class;
struct device* dev;
struct device_node * dev_node;
int gpio_number;
struct semaphore sem_lock; //信号量
};
struct gpio_led gpioled;
static int gpioled_open(struct inode *inode, struct file *filp)
{
down(&gpioled.sem_lock); //获取信号量
filp->private_data = &gpioled;
return 0;
}
static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
char rx_buf[2] = {0};
ret = copy_from_user(rx_buf, buf, count);
if(ret != 0)
{
printk("copy_from_user failed!\n");
}
if(rx_buf[0] == LED_ON)
{
//设置低电平,打开Led
gpio_set_value(gpioled.gpio_number, 0);
}
else if(rx_buf[0] == LED_OFF)
{
//设置高电平,关闭Led
gpio_set_value(gpioled.gpio_number, 1);
}
return 0;
}
static int gpioled_close(struct inode *inode, struct file *filp)
{
struct gpio_led * dev = (struct gpio_led*)(filp->private_data);
up(&dev->sem_lock); //释放信号量
return 0;
}
const struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = gpioled_open,
.release = gpioled_close,
.write = gpioled_write,
};
/*驱动模块入口函数*/
static int __init gpioled_init(void)
{
int ret = 0;
/*初始化信号量,设置信号量值为 1*/
sema_init(&gpioled.sem_lock, 1);
/*1.注册设备号*/
gpioled.major = 0;
if(gpioled.major) //给出主设备号
{
gpioled.devid = MKDEV(gpioled.major, 0);
ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
}
else //未给设备号时,向Linux申请设备号
{
ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
printk("gpioled.major: %d\n", gpioled.major);
printk("gpioled.minor: %d\n", gpioled.minor);
if(ret < 0)
{
printk("apply dev_number failed!\n");
goto apply_devid_failed;
}
/*2.设备初始化 */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_fops);
ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
if(ret < 0)
{
printk("cdev_add failed\n");
goto cdev_add_failed;
}
/*3.自动创建设备节点*/
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if (IS_ERR(gpioled.class)) {
ret = PTR_ERR(gpioled.class);
goto class_create_failed;
}
gpioled.dev = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if (IS_ERR(gpioled.dev)) {
ret = PTR_ERR(gpioled.dev);
goto dev_create_failed;
}
/*读取设备节点*/
gpioled.dev_node = of_find_node_by_path("/gpioled");
if(NULL == gpioled.dev_node)
{
printk("find dev_node failed!\n");
goto read_devnode_failed;
}
/*获取Led所对应的GPIO的编号*/
gpioled.gpio_number = of_get_named_gpio(gpioled.dev_node, "led-gpio", 0);
if(gpioled.gpio_number < 0)
{
printk("get_named_gpio failed!\n");
goto read_devnode_failed;
}
/*申请GPIO管脚 */
ret = gpio_request(gpioled.gpio_number, GPIOLED_NAME);
if(ret != 0)
{
printk("gpio_request failed!\n");
goto read_devnode_failed;
}
/*设置GPIO为输出,设置为高电平,关闭Led */
ret = gpio_direction_output(gpioled.gpio_number, 1);
if(ret != 0)
{
printk("gpio_direction_input failed!\n");
goto set_gpio_input_failed;
}
return 0;
set_gpio_input_failed:
gpio_free(gpioled.gpio_number);
read_devnode_failed:
device_destroy(gpioled.class, gpioled.devid);
dev_create_failed:
class_destroy(gpioled.class);
class_create_failed:
cdev_del(&gpioled.cdev);
cdev_add_failed:
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
apply_devid_failed:
return ret;
}
/*驱动模块出口函数*/
static void __exit gpioled_exit(void)
{
//关闭Led灯
gpio_set_value(gpioled.gpio_number, 1);
/*1. 删除设备*/
cdev_del(&gpioled.cdev);
/*2. 注销设备号*/
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
//3. 销毁设备
device_destroy(gpioled.class, gpioled.devid);
/*4. 销毁类*/
class_destroy(gpioled.class);
/*释放IO */
gpio_free(gpioled.gpio_number);
}
/*驱动入口与出口*/
module_init(gpioled_init);
module_exit(gpioled_exit);
MODULE_LICENSE("GPL"); //License
MODULE_AUTHOR("WeiWuXian"); //author
三. 编译驱动
对上面的驱动进行编译,ubuntu终端进入 8_semphore工程目录下:
wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers/8_semaphore$ make
make -C /home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/wangtian/zhengdian_Linux/Linux_Drivers/8_semaphore modules
make[1]: 进入目录“/home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga”
CC [M] /home/wangtian/zhengdian_Linux/Linux_Drivers/8_semaphore/semaphore.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/wangtian/zhengdian_Linux/Linux_Drivers/8_semaphore/semaphore.mod.o
LD [M] /home/wangtian/zhengdian_Linux/Linux_Drivers/8_semaphore/semaphore.ko
make[1]: 离开目录“/home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga”
wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers/8_semaphore$
可以看出,驱动模块编译通过了。接下来就是对该驱动进行测试,确认所添加的信号量是否可以实现:一次只能有一个应用程序访问 LED 灯。