Linux驱动介绍和实例快速入门

系统 Linux
系统调用是内核和应用程序之间的接口,而驱动程序是内核和硬件之间的接口。它为应用程序屏蔽了硬件的细节,故对应用程序而言,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。

一、驱动简介

Linux的驱动在本质上就是一种软件程序,上层软件可以在不了解硬件特性的情况下,通过驱动提供的接口,和计算机硬件进行通信。

系统调用是内核和应用程序之间的接口,而驱动程序是内核和硬件之间的接口。它为应用程序屏蔽了硬件的细节,故对应用程序而言,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。

Linux驱动程序只是内核的一部分,管理着系统的设备控制器和相应的设备。驱动程序,英文名为"Device Driver",全称“设备驱动程序”,是一种可以使计算机和设备通信的特殊程序,相当于硬件的接口,操作系统只有通过这个接口才能控制硬件设备的工作。它主要完成以下几个功能:

  • 对设备初始化和释放
  • 传送数据到硬盘和从硬件读取数据
  • 检测和处理设备出现的错误

二、驱动分类

计算机系统的硬件由CPU、存储器、和外设组成。驱动针对的对象都是存储器和外设。Linux将外设和存储器分为三个基础大类:块设备驱动,字符设备驱动和网络设备驱动。

2.1、字符设备驱动

字符设备是指那些必须以串行顺序访问的设备,字符设备的I/O操作没有通过缓存。字符设备的操作是以字节为基础的,但一次只能执行一个字节的操作。典型的如LCD、串口、LED、蜂鸣器、触摸屏等等。

2.2、块设备驱动

块设备是相对于字符设备定义的,可以以任意顺序进行访问,以块为单位进行操作。块设备驱动的读写都有缓存来支持,且块设备必须能够随机存取。设备的块大小是设备本身设计时定义好的,软件是不能去更改的,不同设备的块大小可以不一样。常见的块设备都是存储类设备,如:硬盘、NandFlash、iNand、SD等等。

2.3、网络设备驱动

网络设备驱动是专为网卡设计的驱动模型,面向数据包的接收和发送而设计的,它并不应对于文件系统的节点。即不对应于/dev目录下的设备文件,应用程序最终用套间字socket完成与网络设备的接口。

除网络设备外,字符设备和块设备都被映射到Linux文件系统的文件和目录,通过文件系统的系统调用接口open(),write(),read(),close()等即可访问字符设备和块设备。块设备比字符设备复杂,在它上面会首先建立一个磁盘/Flash文件系统,如FAT、EXT3、TAFFS、TFFS等,FAT、EXT3、TAFFS、TFF规范了文件和目录在存储介质上的组织。

三、驱动的编译和加载

 Linux设备驱动属于内核的一部分,Linux内核的一个模块可以以两种方式被编译和加载

3.1、编译方式

内部编译:将驱动程序源码放在内核源码目录中进行编译。

外部编译:将驱动程序源码放在内核源码目录外进行编译。

3.2、加载方式

静态加载:编译进uImage中,系统启动时直接加载。

动态加载:编译.ko文件,动态加载驱动模块。

3.3、编译器

x86等架构使用gcc即可,arm嵌入式设备需要使用相关交叉编译工具链。

下面是内核模块的例子:

#include <linux/module.h>    //所有模块都需要的头文件 
#include <linux/init.h> // init&exit相关宏
static int __init hello_init (void)
{
printk("Hello module init\n");
return 0;
}
static void __exit hello_exit (void)
{
printk("Hello module exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LYB");
MODULE_DESCRIPTION("test for linux driver");

分析上述程序,发现一个Linux内核模块需包含模块初始化和模块卸载函数,前者在insmod的时候运行,后者在rmmod的时候运行。初始化与卸载函数必须在宏module_init和module_exit使用前定义,否则会出现编译错误。

初始化与卸载函数必须在宏module_init和module_exit使用前定义,否则会出现编译错误。程序中的:

  • MODULE_LICENSE(“GPL”)用于声明模块的许可证。
  • MODULE_AUTHOR:说明作者信息.。
  • MODULE_DESCRIPTION:对本驱动的描述。

如果要将其直接编译入Linux内核,则需要将源代码文件拷贝入Linux内核源代码的相应路径里,并修改Makefile。

模块初始化函数的任务是为以后调用模块的函数做准备,好像是模块说,:" 我在这里, 这是我能做的”。

模块的退出函数( 例子里是 hello_exit )就在模块被卸载时调用.,它好像告诉内核, "我不 再在那里了, 不要要求我做任何事了”。

 这种编程的方法类似于事件驱动的编程, 但是虽然不是所有的应用程序都是事件驱动的, 每个内核模块都是。另外一个主要的不同, 在事件驱动的应用程序和内核代码之间, 是退出函数: 一个终止的应用程序可以在释放资源方面 懒惰, 或者完全不做清理工作, 但是模块的退出函数必须小心恢复每个由初始化函数建立的东西, 否则会保留一些东西直到系统重启。

编写Makerfile文件来进行编译:

KERN_DIR ?= /usr/src/linux-headers-$(shell uname -r)/        #内核源码目录/usr/src/linux-headers-$(shell uname -r)/
PWD := $(shell pwd)
obj-m := driverTest.ko
all:
make -C $(KERN_DIR) M=$(PWD) modules
clean:
make -C $(KERN_DIR) M=$(PWD) clean

 3.4、驱动加载、卸载及debug

insmod ./hello.ko    // 加载驱动
lsmod // 查看已加载的驱动
lsmod | grep hello // 使用grep检索过滤
demsg // 查看内核打印信息
demsg | grep hello // 使用grep过滤信息
rmmod hello // 卸载驱动


责任编辑:武晓燕 来源: Android开发编程
相关推荐

2021-02-22 08:36:48

Linux 驱动Fbdev

2023-11-01 08:50:52

DjangoPython

2011-12-22 13:17:03

JavaJFreeChart

2009-11-11 10:27:22

ADO.NET入门

2018-01-08 08:50:05

Linux内核系统程序

2009-06-23 18:09:22

2021-09-07 15:48:28

鸿蒙HarmonyOS应用

2009-12-09 10:50:53

嵌入式Linux

2016-08-24 10:11:00

Linux文件权限

2010-06-17 17:00:07

Linux流量控制

2013-01-15 15:18:46

Linux守护进程

2020-07-23 07:24:40

Kubernetes大数据开发

2017-09-30 16:06:28

代码注解分析

2020-08-12 08:30:20

数据结构算法

2023-08-28 09:14:20

ScrapyPython

2021-06-15 18:42:53

Rollup配置 JavaScript

2020-12-03 08:59:06

Linux设备驱动

2015-10-29 15:36:19

Redis入门

2009-07-20 13:58:07

MySQL JDBC驱

2009-07-07 14:04:55

JSP入门
点赞
收藏

51CTO技术栈公众号