一、信号量

  Linux 内核提供了信号量机制,信号量常常用于控制对共享资源的访问。相比于自旋锁,信号量可以使线程进入休眠状态,使用信号量会提高处理器的使用效率。但是,信号量的开销要比自旋锁大,因为信号量使线程进入休眠状态以后会切换线程,切换线程就会有开销。总结一下信号量的特点:
  ①、因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
  ②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
  ③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的优势。

1.1、信号量 API 函数

  Linux 内核使用 semaphore 结构体表示信号量,结构体内容如下所示:

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

  信号量的 API 函数有:

DEFINE_SEAMPHORE(name)

  定义一个信号量,并且设置信号量的值为 1。

void sema_init(struct semaphore *sem, int val)

  初始化信号量 sem,设置信号量值为 val。

void down(struct semaphore *sem)

  获取信号量,因为会导致休眠,因此不能在中断中使用。

int down_trylock(struct semaphore *sem);

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

int down_interruptible(struct semaphore *sem)

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

void up(struct semaphore *sem)

  释放信号量

二、编写驱动程序

2.1 修改设备树文件

ledtest{#address-cells = <1>;#size-cells = <1>;compatible = "ledtest";pinctrl-names = "default";linux,default-trigger = "input";gpios = <&gpio 26 0>;status = "okay";
};

2.2、LED 驱动修改

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.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/semaphore.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define GPIOLED_CNT            1           /* 设备号个数 */
#define GPIOLED_NAME        "ledtest" /* 名字 */
#define LEDOFF              0           /* 关灯 */
#define LEDON               1           /* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid;         /* 设备号   */struct cdev cdev;        /* cdev     */struct class *class;  /* 类        */struct device *device;    /* 设备    */int major;               /* 主设备号   */int minor;              /* 次设备号   */struct device_node  *nd; /* 设备节点 */int led_gpio;            /* led所使用的GPIO编号        */struct semaphore sem; /* 信号量 */
};struct gpioled_dev gpioled;   /* led设备 *//** @description        : 打开设备* @param - inode     : 传递给驱动的inode* @param - filp   : 设备文件,file结构体有个叫做private_data的成员变量*                       一般在open的时候将private_data指向设备结构体。* @return             : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{filp->private_data = &gpioled; /* 设置私有数据 *//* 获取信号量 */if (down_interruptible(&gpioled.sem)) { /* 获取信号量,进入休眠状态的进程可以被信号打断 */return -ERESTARTSYS;}return 0;
}/** @description      : 从设备读取数据 * @param - filp  : 要打开的设备文件(文件描述符)* @param - buf    : 返回给用户空间的数据缓冲区* @param - cnt  : 要读取的数据长度* @param - offt  : 相对于文件首地址的偏移* @return             : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @description      : 向设备写数据 * @param - filp   : 设备文件,表示打开的文件描述符* @param - buf     : 要写给设备写入的数据* @param - cnt     : 要写入的数据长度* @param - offt  : 相对于文件首地址的偏移* @return             : 写入的字节数,如果为负值,表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev = filp->private_data;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];        /* 获取状态值 */if(ledstat == LEDON) { gpio_set_value(dev->led_gpio, 0);    /* 打开LED灯 */} else if(ledstat == LEDOFF) {gpio_set_value(dev->led_gpio, 1);    /* 关闭LED灯 */}return 0;
}/** @description      : 关闭/释放设备* @param - filp   : 要关闭的设备文件(文件描述符)* @return             : 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{struct gpioled_dev *dev = filp->private_data;up(&dev->sem);     /* 释放信号量,信号量值加1 */return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release =    led_release,
};/** @description : 驱动入口函数* @param       : 无* @return       : 无*/
static int __init led_init(void)
{int ret = 0;/* 初始化信号量 */sema_init(&gpioled.sem, 1);/* 设置LED所使用的GPIO *//* 1、获取设备节点:gpioled */gpioled.nd = of_find_node_by_path("/ledtest");if(gpioled.nd == NULL) {printk("ledtest node not find!\r\n");return -EINVAL;} else {printk("ledtest node find!\r\n");}/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "gpios", 0);if(gpioled.led_gpio < 0) {printk("can't get gpios");return -EINVAL;}printk("gpios num = %d\r\n", gpioled.led_gpio);ret = gpio_direction_output(gpioled.led_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) {       /*  定义了设备号 */gpioled.devid = MKDEV(gpioled.major, 0);register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);} else {                     /* 没有定义设备号 */alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */gpioled.major = MAJOR(gpioled.devid);   /* 获取分配号的主设备号 */gpioled.minor = MINOR(gpioled.devid);  /* 获取分配号的次设备号 */}printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);  /* 2、初始化cdev */gpioled.cdev.owner = THIS_MODULE;cdev_init(&gpioled.cdev, &gpioled_fops);/* 3、添加一个cdev */cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);/* 4、创建类 */gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {return PTR_ERR(gpioled.class);}/* 5、创建设备 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {return PTR_ERR(gpioled.device);}return 0;
}/** @description  : 驱动出口函数* @param       : 无* @return       : 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(&gpioled.cdev);/*  删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);class_destroy(gpioled.class);
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zsx");

2.3、编写测试APP

  测试 APP 使用https://blog.csdn.net/xxxx123041/article/details/120421290中的 App.c 即可。

三、运行测试

  将编译出来的 led.ko文件拷贝到/lib/modules/5.10.17-v7l+/ 目录中,输入如下命令加载 led.ko 驱动模块:

sudo depmod //第一次加载驱动的时候需要运行此命令
sudo modprobe ledtest//加载驱动
dmesg //查看模块打印信息

  驱动加载成功以后会自动在/dev 目录下创建设备节点文件/dev/ledtest。驱动加载成功以后就可以使用 App 软件来测试驱动是否工作正常,输入如下命令以后台运行模式打开 LED 灯,“&”表示在后台运行 App 这个软件:

./App /dev/ledtest 1& //打开 LED 灯

  输出了“App running times:1”和“App running times:2”,这就是模拟 25S 占用,说明 App 这个软件正在使用 LED 灯。此时再输入如下命令关闭 LED 灯:

./App /dev/ledtest 0&//关闭 LED 灯

  注意两个命令都是运行在后台,第一条命令先获取到信号量,因此可以操作 LED 灯,将LED 灯打开,并且占有 25S。第二条命令因为获取信号量失败而进入休眠状态,等待第一条命令运行完毕并释放信号量以后才拥有 LED 灯使用权,将 LED 灯关闭。

树莓派linux led字符设备驱动(信号量)相关推荐

  1. 树莓派linux led字符设备驱动(原子操作)

    一.原子操作   linux驱动中为了处理对共享资源的并发访问,引入了原子操作.原子操作就是指不能再进一步分割的操作,一般原子操作用于变量或者位操作. Linux 内核提供了两组原子操作 API 函数 ...

  2. linux内核字符驱动设备,Linux学习笔记——linux内核字符设备驱动-Go语言中文社区...

    尝试在树莓派安装的raspbian系统上进行linux字符设备驱动 1.更新安装kernel header源码 sudo apt-get update sudo apt-get install ras ...

  3. Linux之字符设备驱动框架

    目录 一.驱动介绍 1.内核模块 2.日志级别 3.模块符号的导出 4.内核模块参数 二.字符设备驱动(一) 1.模块加载 2.注册字符设备驱动 3.内存映射 三.字符设备驱动(二) 1.模块加载 2 ...

  4. linux 编译字符设备驱动错误,linux字符设备驱动框架及编写流程

    流程: init { } exit { } 申请设备号 (动态注册/静态注册) 创建一个字符设备 cdev_alloc 初始化字符设备 cdev_init 设备号和字符设备关联 cdev_add 销毁 ...

  5. Linux SPI 字符设备 驱动例子

    其实,在Linux中,SPI和IIC的注册流程很相似.在这里我还是用iTOP4412做演示. 从原理图可以得知我们要用到的引脚是这几个.用的是SPI_2.记住这个数字,下面设备注册要用到. 首先我们要 ...

  6. 【正点原子MP157连载】第二十章 字符设备驱动开发-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  7. Linux驱动之字符设备驱动

    系列文章目录 第一章 Linux入门之驱动框架 第二章 Linux驱动之字符设备驱动 文章目录 系列文章目录 前言 一.认识字符设备驱动 1.基本概念 2.基本概念 二.字符设备旧框架 1.注册和注销 ...

  8. linux用户空间flash驱动,全面掌握Linux驱动框架——字符设备驱动、I2C驱动、总线设备驱动、NAND FLASH驱动...

    原标题:全面掌握Linux驱动框架--字符设备驱动.I2C驱动.总线设备驱动.NAND FLASH驱动 字符设备驱动 哈~ 这几天都在发图,通过这种方式,我们希望能帮大家梳理学过的知识,全局的掌握Li ...

  9. 一张图掌握 Linux 字符设备驱动架构!【建议收藏】

    目录 一. Linux 中字符设备驱动简介 二. 字符设备驱动快速入门(超简单demo) 1. demo 2. 代码编译 3. 加载驱动模块 4. 创建设备节点文件 5. APP设备文件操作 6. 卸 ...

最新文章

  1. 这是我见过最卡通的 Python 算法了,通俗易懂
  2. centos7 mysql 5.6.38_centos7.4 安装mysql 5.6.38
  3. javascript小数四舍五入
  4. 使用phpize安装php模块
  5. LazyListBoxHelper 动态分页
  6. MySQL month()函数
  7. 华为大数求和 java_大数乘积java
  8. CentOS7 安装git服务器
  9. .Net控件Telerik全套下载:Telerik Controls 2010 Q2 (附加DLL文件+源码)
  10. Sql server 2008 R2 导出/导入数据报错之无法打开全局共享内存以与性能 DLL 通信
  11. C语言中bzero函数
  12. 在minst数据集上定义和训练CNN卷积神经网络,代码+原理+模型更改
  13. linux下搭建redis内网端口映射工具-rinetd
  14. css实现3d旋转图片魔方
  15. /deep/在chrome89+中出现样式混乱的问题
  16. 操作系统 - 虚拟存储管理技术之虚拟页式存储管理
  17. 问题 B: PIPI发工资(拓扑排序遍历)
  18. Django 创建随机验证码
  19. Unity 5.5.0f3发行说明
  20. ps软件怎么测试性能,实际性能测试:Photoshop处理

热门文章

  1. 【扫盲贴】浅谈38K红外发射接受编码(非常好)
  2. 用python提取字符串的中英文——建议收藏反复观看
  3. S3C44B0 寄存器描述
  4. springboot接入华为云短信
  5. 公链,仍然是区块链产业的根基!
  6. js过滤检测敏感词汇
  7. 用什么软件可以修改PDF文件,软件的操作方法
  8. Elasticsearch之Mapping设置详解
  9. 【论文笔记】面向机载应用的多传感器图像融合技术综述
  10. 学习笔记 吴恩达 斯坦福大学公开课 :机器学习课程-1 机器学习的动机与应用