linux字符设备驱动在哪里设置,从点一个灯开始学写Linux字符设备驱动!

原标题:从点一个灯开始学写Linux字符设备驱动!

[导读] 前一篇文章,介绍了如何将一个hello word模块编译进内核或者编译为动态加载内核模块,本篇来介绍一下如何利用Linux驱动模型来完成一个LED灯设备驱动。点一个灯有什么好谈呢?况且Linux下有专门的leds驱动子系统。

点灯有啥好聊呢?

在很多嵌入式系统里,有可能需要实现数字开关量输出,比如:

LED状态显示

阀门/继电器控制

蜂鸣器

......

嵌入式Linux一般需求千变万化,也不可能这些需求都有现成设备驱动代码可供使用,所以如何学会完成一个开关量输出设备的驱动,一方面点个灯可以比较快了解如何具体写一个字符类设备驱动,另一方面实际项目中对于开关量输出设备就可以这样干,所以是具有较强的实用价值的。

要完成这样一个开关量输出GPIO的驱动程序,需要梳理梳理下面这些概念:

设备编号

设备挂载

关键数据结构

设备编号

字符设备是通过文件系统内的设备名称进行访问的,其本质是设备文件系统树的节点。故Linux下设备也是一个文件,Linux下字符设备在/dev目录下。可以在开发板的控制台或者编译的主Linux系统中利用ls -l /dev查看,如下图:

9309b5e184d61baf96453b89ff7d79a2.png

对于ls -l列出的属性,做一个比较细的解析:

3ee5e7b83d4d4b65b94a867c9742127e.png

细心的朋友或许会发现设备号属性,在有的文件夹下列出来不是这样,这就对了!普通文件夹下是这样:

f3ff185e2d64379d58496eb8dd37a54c.png

差别在于一个是文件大小,一个是设备号。

再细心一点的朋友或许还会问,这些/dev下的文件时间属性为神马都相差无几?这是因为/dev设备树节点是在内核启动挂载设备驱动动态生成的,所以时间就是系统开机后按次序生成的,你如不信,不妨重启一下系统在查看一下。

96cd5b24df35de675c885745f1f7896f.png

常见文件类型:

d: directory 文件夹

l: link 符号链接

p: FIFO pipe 管道文件,可以用mkfifo命令生成创建

s: socket 套接字文件

c: char 字符型设备文件

b: block 块设备文件

-:常规文件

回到设备号,设备号是一个32位无符号整型数,其中:

12位用来表示主设备号,用于标识设备对应的驱动程序。

20位用来表示次设备号,用于正确确定设备文件所指的设备。

这怎么理解呢,看下串口类设备就比较清楚了:

365544e6159d9bf06c9ccb6482ff7edc.png

主设备号一样证明这些设备共用了一个驱动程序,而次设备号不一样,则对应了不同的串口设备。那么怎么得到设备号呢?

/*下列定义位于./include/linux/types.h */

typedefu32 __kernel_dev_t;

typedef__kernel_dev_tdev_t;

/* 下面宏用于生成主设备号,次设备号 */

/* 下列定义位于./include/linux/Kdev_t.h */

# defineMINORBITS 20

# defineMINORMASK ((1U << MINORBITS) - 1)

# defineMAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))

# defineMINOR(dev) ((unsigned int) ((dev) & MINORMASK))

# defineMKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

使用举例:

/* 主设备号 */

MAJOR( dev_tdev);

/* 次设备号 */

MINOR( dev_tdev);

设备挂载

为简化问题,本文描述一下动态加载设备驱动模块,暂不考虑设备树。参考<>一书。可参照前文将驱动编译成模块,然后利用下面脚步动态加载模块。由前面描述,知道设备最终需要在/dev目录下生成一个设备文件,那么这个设备文件节点是怎么生成呢,看看下面的脚本:

#!/bin/sh

#-----------------------------------------------------------------------

module="led"

device="led"

mode="664"

group="staff"

# 利用insmod命令加载设备模块

insmod -f $module.ko $* || exit 1

#获取系统分配的主设备号

major=`cat /proc/devices | awk "$2=="$module" {print $1}"`

# 删除旧节点

rm -f /dev/${device}

# 创建设备文件节点

mknod /dev/${device} c $major 0

#设置设备文件节点属性

chgrp $group /dev/${device}

chmod $mode /dev/${device}

这里要提一下/proc/devices,这是一个文件记录了字符和块设备的主设备号,以及分配到这些设备号的设备名称。比如使用cat命令来列出这个文件内容:

e2e0674a8c3af781b601ded2a9dd4fe5.png

关键数据结构

字符设备由什么关键数据结构进行抽象的呢,来看看:

file_operations定义在./include/linux/fs.h

cdev定义在./include/linux/cdev.h

e71a5648094b83bf9d279c1a980f93fb.png

cdev中与字符设备驱动编程相关两个数据域:

const struct file_operations *ops;

dev_t dev;设备编号

文件操作符是一个庞大的数据结构,常规字符设备驱动一般需要实现下面一些函数指针:

read:用来实现从设备中读取数据

write:用于实现写入数据到设备

ioctl:实现执行设备特定命令的方法

open:用实现打开一个设备文件

release:当file结构被释放时,将调用这个接口函数点灯设备

先上代码(可左右滑动显示):

# include

# include

# include

# include /* printk */

# include

# include

# include /* everything... */

# include

# include /* copy_*_user */

/*这里具体参考不同开发板的电路 GPIOC24 */

# defineLED_CTRL (2*32+24)

staticconstunsignedintled_pad_cfg = LED_CTRL;

structt_led_dev{

structcdevcdev;

unsignedcharvalue;

};

structt_led_devled_dev;

staticdev_tled_major;

staticdev_tled_minor= 0;

staticintled_open(struct inode * inode,struct file * filp)

{

filp->private_data = &led_dev;

printk ( "led is opened!n");

return0;

}

staticintled_release(struct inode * inode,

struct file * filp)

{

return0;

}

staticssize_t led_read(struct file * file,

char__user * buf,

size_tcount,

loff_t*ppos)

{

ssize_tret= 1;

if(copy_to_user(&(led_dev.value),buf, 1))

return-EFAULT;

printk ( "led is read!n");

returnret;

}

staticssize_t led_write(struct file * filp,

constchar__user *buf,

size_tcount, loff_t*ppos)

{

unsignedcharvalue;

ssize_tretval = 0;

if(copy_from_user(&value,buf, 1))

return-EFAULT;

if(value& 0x01)

gpio_set_value(led_pad_cfg, 1);

else

gpio_set_value(led_pad_cfg, 0);

printk ( "led is written!n");

returnretval;

}

staticconststructfile_operationsled_fops= {

.owner = THIS_MODULE,

.read = led_read,

.write = led_write,

.open = led_open,

.release = led_release,

};

staticvoidled_setup_cdev(struct t_led_dev * dev, intindex)

{

/* 初始化字符设备驱动数据域 */

interr,devno = MKDEV(led_major,led_minor+index);

cdev_init(&(dev->cdev),&led_fops);

dev->cdev.owner = THIS_MODULE;

dev->cdev.ops = &led_fops;

/* 字符设备注册 */

err = cdev_add(&(dev->cdev),devno, 1);

if(err)

printk(KERN_NOTICE "Error %d adding led %d",err,index);

}

staticintled_gpio_init( void)

{

if(gpio_request(LED_CTRL, "led") < 0) {

printk( "Led request gpio failedn");

return-1;

}

printk( "Led gpio requested okn");

gpio_direction_output(LED_CTRL, 1);

gpio_set_value(LED_CTRL, 1);

return0;

}

/* 注销设备 */

voidled_cleanup( void)

{

dev_tdevno = MKDEV(led_major, led_minor);

gpio_set_value(LED_CTRL, 0);

gpio_free(LED_CTRL);

cdev_del(&led_dev.cdev);

unregister_chrdev_region(devno, 1); //注销设备号

}

/* 注册设备 */

staticintled_init( void)

{

intresult;

dev_tdev = MKDEV( led_major, 0);

/* 动态分配设备号 */

result = alloc_chrdev_region(&dev, 0, 1, "led");

if(result< 0)

returnresult;

led_major = MAJOR(dev);

memset(&led_dev, 0, sizeof(struct t_led_dev));

led_setup_cdev(&led_dev, 0);

led_gpio_init;

printk ( "led device initialised!n");

returnresult;

}

module_init(led_init);

module_exit(led_cleanup);

MODULE_DEION( "Led device demo");

MODULE_AUTHOR( "embinn");

MODULE_LICENSE( "GPL");

来总结一下要点:

init函数,需要用module_init宏包起来,本例中即为led_init,module_init宏的作用就是选编译为模块或进内核的底层实现,建议刚开始不必深究。一般而言主要实现:

申请分配主设备号alloc_chrdev_region

为特定设备相关数据结构分配内存

将入口函数(open read write等)与字符设备驱动的cdev抽象数据结构关联

将主设备与驱动程序cdev相关联

申请硬件资源,初始化硬件

调用cdev_add注册设备

exit函数,一样需要用module_exit包起来,主要负责:

释放硬件资源

调用cdev_del删除设备

调用unregister_chrdev_region注销设备号

用户空间与驱动数据交换

copy_to_user,如其名一样,将内核空间数据信息传递到用户空间

copy_from_user,如其名一样,从用户空间拷贝数据进内核空间

善用printk进行驱动调试,这是内核打印函数。

gpio相关操作函数,这里就不一一列举其作用了,比较容易理解。测试驱动# include

# include

# include

# include

# defineREAD_SIZE 10

intmain( intargc, char**argv){

intfd,count;

floatvalue;

unsignedcharbuf[READ_SIZE+ 1];

printf( "Cmd argv[0]:%s,argv[1]:%s,argv[2]:%sn",argv[ 0],argv[ 1],argv[ 2] );

if( argc< 2){

printf( "[Usage: test device_name ]n");

exit( 0);

}

if( strlen(argv[ 2]!= 1)

printf( "Invalid parametern");

if(( fd = open(argv[ 1],O_WRONLY ))< 0){

printf( "Error:can not open the device: %sn",argv[ 1] );

exit( 1);

}

if(argv[ 2][ 0] == '1')

buf[ 0] = 1;

elseif(argv[ 2][ 0] == '0')

buf[ 0] = 0;

else

printf( "Invalid parametern");

printf( "write: %dn",buf[ 0]);

if( (count = write( fd, buf , 1))< 0){

perror( "write error.n");

exit( 1);

}

close(fd);

printf( "close device %sn",argv[ 1] );

return0;

}

编译成可执行文件,调用前面的脚本加载设备后,在/dev下就可以看到led设备了。 比如测试代码编译成ledTest执行文件,则使用下面命令运行测试程序就可以看到led控制效果了:

/*打开led 具体取决电路是高有效还是低有效*/

./ledTest /dev/led 1

./ledTest /dev/led 0

这样就实现了用户空间驱动底层设备了,实际应用代码就可以这样去访问底层的字符型设备。

总结一下

本文总结了简单字符设备的驱动开发的一些要点,以及如何动态加载,在设备文件系统树上创建设备节点,并演示了驱动以及驱动使用的基本要点。

免责声明:本文系网络转载,版权归原作者所有。如涉及作品版权问题,请与我们联系,我们将根据您提供的版权证明材料确认版权并支付稿酬或者删除内容。返回搜狐,查看更多

责任编辑:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ipv6 ospf配置方法_【思唯网络学院】网络故障大全及处理原理和方法

第一章 故障处理方法一、网络的复杂性   一般网络包括路由、拨号、交换、视频、WAN(ISDN、帧中继、ATM、…)、LAN、VLAN、… 二、故障处理模型   1、 界定问题(Define the Problem)   详细而精确地描述故障的症状和潜在的原因   2、 收集详细信息(Gather Facts)R>信…

怎么让模糊的数字变清楚_一键模糊图像变清晰,好家伙!这款神器插件你值得拥有...

让我们结伴&#xff0c;走进设计好家伙&#xff0c;最近有设计师朋友给我反映很多问题!其中吐槽最多的就是甲方给图不够清晰整个模特&#xff0c;产品都有种朦胧美我擦嘞&#xff0c;导入到软件作图放大后&#xff0c;像素啥都看不清有木有~&#xff01;&#xff01;找老板要清…

vscode php插件_JS之 提高开发效率的Visual Studio Code插件

阅读本文约需要9分钟大家好&#xff0c;我是你们的导师&#xff0c;我每天都会在这里给大家分享一些干货内容(当然了&#xff0c;周末也要允许老师休息一下哈)。上次老师跟大家分享了JS之 小技巧的知识&#xff0c;今天跟大家分享下JS之 提高开发效率的Visual Studio Code插件的…

antd table设置表格一个单元格的字体颜色_alireacttable:高性能 React 表格组件

点击上方蓝字关注我们简介在前端开发中&#xff0c;表格一直都是最复杂的组件之一。表格不仅要支持丰富的操作(排序、过滤、搜索、分页、自定义列等)&#xff0c;还要有非常好的性能以展示大量数据。很多组件库(例如 fusion design&#xff0c;ant design)提供了功能丰富的表格…

linux修改arena大小,Resolume Arena怎么设置大屏幕 调整画面屏幕的方法

如果你想要制作VJ视频&#xff0c;那么Resolume Arena绝对可以满足你的所有需求&#xff0c;小编近期了解到很多用户不知道怎么设置大屏幕&#xff0c;如果你还不知道具体的操作方法&#xff0c;就赶快来看看下面的教程吧&#xff01;操作步骤如下&#xff1a;1、如果你在使用R…

for each循环_Power Query — 循环初步

题记&#xff1a;《Excel圣经》1:3 微软说&#xff0c;“要有循环”&#xff0c;便有了循环。引子&#xff1a;keyword: one of and as each else error false if in is let meta not otherwise or section shared then true try type #binary #date #datetime #datetimezone #…

linux 直接映射 页表大小,linux 启动过程临时页表到底映射了多大内存?

从linux-2.4内核开始&#xff0c;在建立临时页表的时候&#xff0c;一般的教科书都说是映射了8M的物理内存&#xff0c;但是为什么是映射8M呢&#xff1f;当时网上有资料说&#xff0c;8M足够了&#xff0c;但为什么就足够了&#xff0c;一直没有彻底搞清楚&#xff0c;今天又重…

字符串字段当条件查询的时候需要加引号吗_如此详细的SQL优化教程,是你需要的吗?...

基础数据准备二&#xff1a;五百万数据插入上面插入几条测试数据&#xff0c;在使用索引时还需要插入更多的数据作为测试数据&#xff0c;下面就通过存储过程插入500W条数据作为测试数据三&#xff1a;使用索引和不使用索引的比较没有添加索引前一个简单的查询用了1.79秒创建索…

使用CoreImage教程

使用CoreImage教程 CoreImage包含有很多实用的滤镜,专业处理图片的库,为了能看到各种渲染效果,请使用如下图片素材. 现在可以开始教程了: #define FIX_IMAGE(image) fixImageWidth(image, 320.f)// 固定图片的宽度 UIImage * fixImageWidth(UIImage *image, CGFloat width) {f…

电脑生成siri语音_米家智能台灯1S全新升级,支持小爱和Siri的语音控制

夜晚的灯光是我们最为需要的东西&#xff0c;但很多时候&#xff0c;我们需要灯照在不同的地方&#xff0c;平时我只靠吸顶灯的光来照亮家里的每一个角落&#xff0c;甚至是看书的时候都只靠吸顶灯照明。台灯作为占用面积小&#xff0c;光照均匀&#xff0c;让很多的人越来越依…

linux vnc检查,检查Ubuntu VNC设置(避免远程登陆)

(1)安装x11vncsudo apt-get install x11vnc(2)将x11vnc加入xinetdsudo gedit /etc/xinetd.d/x11vnc加入下面这段service x11vnc{ port 5900 type UNLISTED socket_type stream protocol tcp wait …

JavaScript网络地址作为参数_JavaScript之bind的模拟实现

阅读本文约需要5分钟大家好&#xff0c;我是你们的导师&#xff0c;我每天都会给大家分享一些干货内容(当然了&#xff0c;周末也要允许老师休息一下哈)。昨天给大家分享了JavaScript的call和apply的模拟实现&#xff0c;今天给大家分享一下bind的模拟实现。什么是bind&#xf…

cdh集群linux命令,CDH集群中,服务器启动spark2-shell命令行注意事项

1、环境cdh5.12.3spark2 2.3.02、需要本地地洞spark2-shell用于环境测试错误一&#xff1a;Error: A JNI error has occurred, please check your installation and try againException in thread "main" java.lang.NoClassDefFoundError: org/slf4j/Loggerat java.l…

python语音转文字软件_python编写语音转文字软件|语音转文字工具免费版下载(语音批量转换文字) v2.0 最新版_数码资源网...

没有专业的工具怎么能够将语音转换为文字呢&#xff1f;小编为大家提供了语音转文字工具免费版&#xff0c;一款通过Python编写语音转文字软件。用户通过使用语音转文字工具免费版&#xff0c;可以将语音批量转换文字&#xff0c;而且操作也是非常的简单&#xff0c;如果你需要…

Spring-bean作用域scope详解

2019独角兽企业重金招聘Python工程师标准>>> 默认情况下&#xff0c;从bean工厂所取得的实例为Singleton&#xff08;bean的singleton属性&#xff09; Singleton: Spring容器只存在一个共享的bean实例&#xff0c;是默认的配置。 Prototype: 每次对bean的请求都会创…

c语言怎么写星星代码,C语言打印星星的问题

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼#include #include int main(void){int index, up, lines;printf("请输入将要显示的行数:\n");while(scanf("%d", &up) 1){if(up % 2 0){for(lines 1; lines < up / 2; lines){for(index 0; index …

c语言计算机猜数字100以内,求一个猜数字C语言代码,要求如下 计算机生成一个100以内的随机数,玩家来猜 记录猜的次数,最后打...

满意答案itpotato推荐于 2017.10.09采纳率&#xff1a;51% 等级&#xff1a;12已帮助&#xff1a;4600人/**百度知道越来越水了&#xff0c;这么简单的题就一个回答*没见过限定头文件数目的。。而且是限定至少。。。。*/#include#include#include#includeint getrand(){sran…

fakeapp2.2.0下载_软件下载 | SuperCuger 测量平差系统 V1.0

SuperCuger测量平差系统 version 1.0 是一款基于测绘工程中边角网、测角网、测边网、水准网测量数据的免费开源的可视化平差系统。可用于测绘工程中测量平差计算和平差结果报告生成&#xff0c;和插件(中间件)模式扩展新的平差功能。我们的软件具备平差数据可视化&#xff0c;便…

c语言cin输入数组,C++基础:各种输入方法总结cin.get()、

原标题&#xff1a;C基础&#xff1a;各种输入方法总结cin.get()、在C中&#xff0c;各种输入方法还是不少的&#xff0c;而且各有所异&#xff0c;本文做一点简要总结&#xff0c;主要涉及如下内容&#xff1a;cin、cin.get()、cin.getline()、getline()、gets()、getchar()。…

手机怎么能把书签导出来_成人高考能在手机上报名吗?成人高考怎么缴费?

很多成人高考的考生在报名考试的时候想用手机进行报名&#xff0c;但是听说都是使用电脑报名&#xff0c;所以不知道手机报名行不行&#xff0c;另外还有很多考生也不知道如何缴费。那么成人高考能在手机上报名吗?成人高考怎么缴费?下面小编就来和大家聊一聊成人高考手机报名…