驱动程序(Device Driver)是一种可以使计算机和设备通信的特殊程序,相当于内核和硬件之间的接口,操作系统只能通过这个接口,才能控制硬件设备的工作。驱动程序接受上层软件(应用程序、内核)的请求,完成对硬件的操作,屏蔽了硬件的细节。Linux平台下的驱动程序将硬件设备抽象成一个文件,应用程序操作文件即操作硬件。

1 Linux驱动分类

linux系统下驱动分为字符设备驱动、块设备驱动和网络设备驱动三种,以下分别介绍三种驱动。

1.1 字符设备驱动

字符设备是指存取时没有缓存的设备,采用字符流方式访问的设备,此类设备支持按字节/字符来顺序读写数据,通常不支持随机存取 ,常见的字符设备有:led、key、camera、显卡、串口等。

1.2 块设备驱动

应用程序与驱动程序之间的数据交互是以块为单位的,应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。常见的块设备有:U盘、eMMC、SD卡等,应用程序可以寻址磁盘上的任何位置,并由此读取数据。

1.3 网络设备驱动

数据包传输方式访问的设备,和上两者不同,通过ifconfig来创建和配置设备。网络驱动同块驱动最大的不同在于网络驱动异步接受外界数据,而块驱动只对内核的请求作出响应。常见的网络设备驱动有:蓝牙、wifi、网卡等。

2 Linux字符驱动开发框架

在Linux内核中,使用cdev结构体来描述一个字符设备,该结构体的定义在include/linux/cdev.h文件中,cdev结构体中的成员如下:

struct cdev {struct kobject kobj;  struct module *owner;                 //该字符设备所在的内核模块的对象指针const struct file_operations *ops;    //该结构描述了字符设备所能实现的方法struct list_head list;     dev_t dev;                            //字符设备的设备号,由主设备号和次设备号构成unsigned int count;                   //隶属于同一主设备号的次设备号的个数
};

其中比较重要的是file_operations这个结构体,每个设备驱动都实现这个结构体中所定义的部分或全部函数,不同的版本的内核file_operaTIons定义会稍有不同,linux 3.6中的定义如下:

struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
};

当应用程序对设备文件进行诸如open、close、read、write等操作时,Linux内核将通过file_operations结构访问驱动程序提供的函数。例如,当应用程序对设备文件执行读操作时,内核将调用file_operations结构中的read函数。因此,驱动开发就是相当于在实现驱动的入口和出口函数基础上再把file_operations结构体中的函数实现,入口、出口函数在驱动模块加载和卸载时调用,file_operations结构体中的函数在设备控制时调用。

3 Linux字符驱动实例

在linux源码drivers/char目录下新建first_driver.c文件,文件中内容如下所示:

#include <linux/fs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <asm/io.h>static struct class *first_drv_class;
static struct device *first_drv_class_dev;static dev_t devno; //设备号
static int major = 231; //主设备号
static int minor = 0; //次设备号
static char *module_name = "first_driver"; //模块名static int first_drv_open(struct inode *inode, struct file *file)
{printk("first_drv_open\n");return 0;
}static int  first_drv_write(struct file *file, const char __user *buf, size_t
count, loff_t *ppos)
{printk("first_drv_write\n");return 0;
}static struct file_operations first_drv_fops =
{.owner = THIS_MODULE,// 将对应的函数关联在file_operations的结构体中.open = first_drv_open,.write = first_drv_write,
};
//驱动入口
static int  first_drv_init(void)
{int ret;devno = MKDEV(major,minor); //创建设备号ret = register_chrdev( major, module_name, &first_drv_fops); //注册驱动告诉内核把这驱动加入到内核的链表中first_drv_class = class_create( THIS_MODULE, "first_driver_demo" ); //让代码在/dev下自动生成设备  (创建一个类)first_drv_class_dev = device_create( first_drv_class, NULL, devno, NULL, module_name); //创建设备文件(在类下面生成一个设备)return 0;
}static void  first_drv_exit(void)
{printk("first_drv_exit\n");device_destroy(first_drv_class,devno);class_destroy(first_drv_class);unregister_chrdev( major, module_name);
}
//内核将通过这个宏,来直到这个驱动的入口和出口函数
module_init(first_drv_init);
module_exit(first_drv_exit);MODULE_AUTHOR("zhy <1521772422@qq.com>");
MODULE_LICENSE("GPL");

4 Linux字符驱动编译

我使用了友善之臂2416的开发板,参考了提供的用户手册中的驱动模块编译方式,具体步骤如下:
1 在linux-3.6/drivers/char/kconfig 文件中config MINI2451_ADC后面添加first_driver的config:

config MINI2451_ADCtristate "ADC driver for FriendlyARM Mini2451 development boards"depends on MACH_MINI2451default yhelpthis is ADC driver for FriendlyARM Mini2451 development boardsconfig MINI2451_FIRST_DRIVERtristate "My first driver for FriendlyARM Mini2451 development boards"depends on MACH_MINI2451default yhelpthis is demo driver for FriendlyARM Mini2451 development boards

2 在linux3.6目录执行:

cp mini2451_linux_config .config
make menuconfig

出现kernal配置界面。
3 进入Device Drivers -》Character devices ,菜单中就可以看到刚才所添加的选项了。按下空格键将会选择为" M “,此意为要把该选项编译为模块方式;再按下空格会变为” * “,意为要把该选项编译到内核中,在此我们选择” M "即可,如图:

4 在linux-3.6/drivers/char/Makefile最末尾添加first_driver的编译选项。

obj-$(CONFIG_MINI2451_BUTTONS)   += mini2451_buttons.o
obj-$(CONFIG_MINI2451_BUZZER)   += mini2451_pwm.o
obj-$(CONFIG_MINI2451_ADC)  += mini2451_adc.o
obj-$(CONFIG_MINI2451_FIRST_DRIVER)  += first_driver.o

5 在linux3.6目录执行:

make modules

就可以在linux-3.6/drivers/char目录生成所需要的内核驱动文件first_driver.ko了。将编译出的first_driver.ko拷贝到开发板/lib/modules/3.6.0-FriendlyARM 目录。

cp /sdcard/first_driver.ko /lib/modules/3.6.0-FriendlyARM/
cd lib/modules/3.6.0-FriendlyARM/

然后在板子中使用insmod命令加载写好的驱动程序:

insmod first_driver.ko

执行后没有任何打印出来,按理加载模块时会调用first_drv_init函数,会有printk打印出来,但是控制台没有任何消息。
使用lsmod命令查看发现驱动已经被加载:

lsmod

在/dev/目录下也能发现first_driver这个设备

网上查找博客发现应该时printk log等级设置的原因,使用demsg查看内核日志:

demsg

first_drv_init函数调用时的log果然有被打印,所以是printk log等级设置过低的原因,先查看一下当前打印等级。

cat /proc/sys/kernel/printk

显示出的4个数据分别对应控制台日志级别、默认的消息日志级别、最低的控制台日志级别和默认的控制台日志级别。
使用下面的命令设置当前日志级别:

echo 8 > /proc/sys/kernel/printk

这样所有级别 < 8的消息都可以显示在控制台上,但是一顿操作后仍然打印不出来,原因没有查明,所以还是老老实实的使用dmesg命令查看。
如果想卸载模块,执行以下命令:

rmmod first_driver

注意 : 要使模块能够正常卸载 , 必须把模块放入开发板/lib/modules/3.6.0-FriendlyARM 目录。

5 Linux字符驱动测试

驱动程序写完了,编写测试程序调用一下驱动,以下是测试程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>int main(int argc, char **argv)
{int fd;      //声明设备描述符int val = 1;  //随便定义变量传入到//根据设备描述符打开设备,之前定义的为first_driverfd = open("/dev/first_driver", O_RDWR);  if(fd < 0)          //打开失败printf("can't open\n");  write(fd, &val, 4);  //根据文件描述符调用writereturn 0;
}

编译测试程序:

arm-linux-gcc driver_test.c -o driver_test

将编译好的driver_test拷贝到开发板中运行,。

/sdcard/driver_test

使用dmesg 命令查看log

可以看到first_drv_open和first_drv_write函数有被调用,证明编写的驱动程序ok。

参考文章

1 驱动程序
2 Linux驱动主要类型简介
3 Linux驱动分类简介
4 详细到吐血 —— 树莓派驱动开发入门:从读懂框架到自己写驱动
5 linux内核打印数据到串口控制台,printk数据不打印问题

Linux字符型驱动开发—基于友善之臂2416开发板相关推荐

  1. 基于Gpio的Linux字符型驱动设计--…

    流水灯Linux驱动步骤 第一步:编写字符设备驱动 第二步:加载 第三步:编写应用程序测试设备驱动 第一步:编写流水灯Linux驱动  在/linux-3.2/driver/char/sep4020_ ...

  2. 手把手教linux设备驱动,手把手教你写Linux设备驱动---定时器(一)(基于友善之臂4412开发板)...

    这个专题我们来说下Linux中的定时器. 在Linux内核中,有这样的一个定时器,叫做内核定时器,内核定时器用于控制某个函数,也就是定时器将要处理的函数在未来的某个特定的时间内执行.内核定时器注册的处 ...

  3. linux字符型驱动开发体验01

    步骤: 1.编写驱动代码 2.编译驱动代码(需要用到makefile,这里我就不提供了,谁需要可以留言) 3.编写并编译测试代码 4.添加内核模块 sudo insmod mychar.ko 5.查看 ...

  4. 番外篇——字符型驱动开发

    [番外篇]字符型驱动开发 前言 开发简单的字符型驱动 前言 驱动程序在编译时需要依赖编译好的内核文件,如果内核文件和实际的开发板对不上,则很可能导致加载驱动失败.所以一旦对设备的内核或设备树进行了更改 ...

  5. linux内核定义注册设备,linux字符型设备驱动 一.注册设备并创建设备文件

    1.字符设备 字符设备.字符设备驱动与用户空间访问该设备的程序三者之间的关系 Linux内核中: a -- 使用cdev结构体来描述字符设备; b -- 通过其成员dev_t来定义设备号(分为主.次设 ...

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

    原标题:从点一个灯开始学写Linux字符设备驱动! [导读] 前一篇文章,介绍了如何将一个hello word模块编译进内核或者编译为动态加载内核模块,本篇来介绍一下如何利用Linux驱动模型来完成一 ...

  7. Linux字符设备驱动模型

    版本 颁布日期 修订章节 0.1 2015.08.13 撰写草稿 0.2 2015.12.07 整合字符型设备驱动 0.3 2015.12.07 整理文档,尚未验证驱动源码 0.4 2016.02.2 ...

  8. Linux 字符设备驱动结构(三)—— file、inode结构体及chardevs数组等相关知识解析

    前面我们学习了字符设备结构体cdev Linux 字符设备驱动开发 (一)-- 字符设备驱动结构(上)  下面继续学习字符设备另外几个重要的数据结构. 先看下面这张图,这是Linux 中虚拟文件系统. ...

  9. linux设备模型 字符设备,Linux 字符设备驱动模型之框架解说

    一.软件操作硬件设备模型 在进行嵌入式开发的过程中,在常做的事情就是驱动配置硬件设 备,然后根据功能需求使用硬件设备,实现功能的逻辑.如下图为其 相互之间的关系. 如上图所示: 驱动程序:主要作为操作 ...

最新文章

  1. 判断为空:null、undefined、空字符串、中文空格
  2. NTU课程笔记 MAS714(2) Big-O notations
  3. 2021高考成绩微信查询方法6,2021年高考成绩查询时间、入口及方法
  4. 打开mobilenet——ssd的demo.py显示这样的错误解决方法:Intel MKL FATAL ERROR: Cannot load libmkl_avx.so or libmkl_def.s
  5. 淘宝JavaScript 编码风格规范
  6. jdba访问mysql_mysql连接出现问题记录
  7. jmeter JDBC 连接数据库
  8. 放弃微服务,构建单体应用
  9. 论ul、ol和dl的区别
  10. java模拟多个用户操作,JAVA 模拟多用户提交动作
  11. Python入门--文件的读写,相对路径,绝对路径
  12. labuladong的算法小抄_学会了回溯算法,我终于会做数独了
  13. EC20模块内置协议栈的开发笔记
  14. 阅读笔记-平凡的世界
  15. 《Linux学习一》交叉编译工具链的构建工具Crosstool-ng
  16. C#中操作Word(8)—— 向Word中插入图表的三种方法(一)
  17. web前端开发新技术,CSS介绍
  18. 09驾校科目一考试系统——提交分数
  19. 逻辑回归优化算法总结一
  20. mysql vacuum_Vacuum 和 Vacuum Full 的处理过程

热门文章

  1. unity3d 重要类+方法 。。。再来一遍吧。。。
  2. 离线计算机知识,2016浙大计算机应用基础离线1.计算机基础知识题OK.docx
  3. DIY 一个树莓派无人机
  4. 美女图片在网站上的应用
  5. OSSIM安装使用教程(OSSIM-5.6.5)
  6. 究竟是量化模型重要还是人重要?
  7. AE2020 TypeMonkey V1.18脚本安装
  8. Netbackup常用命令--bprestore
  9. bim 水利枢纽 运维_BIM水坝工程技术案例:那棱格勒河水利枢纽工程BIM应用汇报...
  10. windows系统下超级好用的欧路词典,足以秒杀有道!