人人范文网 范文大全

嵌入式Linux驱动

发布时间:2020-03-03 10:18:20 来源:范文大全 收藏本文 下载本文 手机版

嵌入式Linux驱动

1 简介

设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样使硬件对应用程序来说是透明的,在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。设备驱动程序是嵌入式Linux内核的一部分,它完成以下的功能: · 对硬件设备初始化和释放

· 把数据从内核传送到硬件,或从硬件读取数据

· 读取应用程序传送给设备文件的数据和回送应用程序请求的数据 ·检测和处理设备出现的错误和异常

Linux系统的设备分为字符设备,块设备和网络设备。用户进程通过设备文件实现与硬件的交流。每个设备文件都有其文件属性,表示是字符设备还是块设备。另外每个文件都有两个设备号:第一个是主设备号,标识驱动程序;第二个是从设备号,标识使用同一个设备驱动程序的不同硬件设备。

设备驱动程序可以分为3个主要组成部分: (1) 自动配置和初始化

用于负责检测所要驱动的硬件设备是否存在和是否能正常工作。如果该设备正常,则对这个设备及其相关设备驱动程序需要的软件状态进行初始化,如设置寄存器的值,初始化驱动程序用到的数据结构。这部分驱动程序仅在初始化的时候被调用一次。

(2)服务于I/O请求的子程序

调用服务于I/O请求的子程序是由于系统调用的结果,如read,write调用。这部分程序在执行的时候,系统仍认为是和进行调用的进程属于同一个进程,只是由用户态变成了核心态,它们的运行环境和进行此系统调用的用户程序一样,因此可以在其中调用sleep()等与进程运行环境有关得函数。 (3)中断服务子程序

在UNIX系统中,并不是直接从中断向量表中调用设备驱动程序的中断服务子程序,而是由UNIX系统来接收硬件中断,再由系统调用中断服务子程序。中断可以发生在任何一个进程运行的时候,因此在中断服务程序被调用的时候,不能依赖于任何进程的状态,也就不能调用任何与进程运行环境有关的函数。因为设备驱动程序一般支持同种类型的若干设备,所以一般在系统调用中断服务子程序的时候,都带有一个或多个参数,以唯一标识请求服务的设备。 2 一个简单的Linux2.6内核驱动模块(hello world) /* hello.c */ #include /* Needed by all modules */ #include /* Needed for KERN_ALERT */ #include /* Needed for the module-macros */ static int __init hello_init(void) // Module entry function specified by module_init() { printk(KERN_ALERT \"Hello,world!n\"); return 0; } static void __exit hello_exit(void) //Module exit function specified by module_exit() { printk(KERN_ALERT \"Goodbye,cruel world!n\"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE(\"Dual BSD/GPL\"); //should always exist or you’ll get a warning MODULE_AUTHOR(\"BENSON\"); //optional MODULE_DESCRIPTION(\"STUDY_MODULE\"); //optional /* Makefile */ # Makefile 2.6 obj-m = hello.o KDIR:=/lib/modules/$(shell uname -r)/build #PWD=$(shell pwd) all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean ************************************************ obj-m := hello.o表示编译后生成hello.o模块。

$(KDIR) 指定了内核源码的路径,“M=”表示这是个外部模块,M=$(PWD) 指定了该模块文件所在的路径。

注: makefile预定义了$(PWD)变量,此处可以不必重复定义。 执行#make编译成功后 加载模块

#insmod hello.ko 字串7 #lsmod 输出内核已加载模块信息,可以查看到刚刚加载成功的hello模块 „„

Module Size Used by hello 5632 0 可以在日志里查看加载模块时的信息 #vi /var/log/meages „„

Sep 27 13:25:21 localhost kernel: Hello,world! 卸载模块 #rmmod hello.ko #lsmod 发现hello模块已经被卸载 查看日志信息

#vi /var/log/meages „„

Sep 27 13:26:58 localhost kernel: Goodbye,cruel world! 3 一个简单Linux2.6内核的字符驱动程序

Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得Windows的设备操作犹如文件一般。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作,如open ()、close ()、read ()、write () 等。

Linux主要将设备分为二类:字符设备和块设备。字符设备是指设备发送和接收数据以字符的形式进行;而块设备则以整个数据缓冲区的形式进行。字符设备的驱动相对比较简单。 //globalvar.c #include #include #include #include MODULE_LICENSE(\"GPL\"); #define MAJOR_NUM 254 //主设备号

static ize_t globalvar_read(struct file *, char *, size_t, loff_t*); static ize_t globalvar_write(struct file *, const char *, size_t, loff_t*); //初始化字符设备驱动的file_operations结构体 struct file_operations globalvar_fops = { read: globalvar_read, write: globalvar_write, }; static int global_var = 0; //\"globalvar\"设备的全局变量 static int __init globalvar_init(void) { int ret;

//注册设备驱动

ret = register_chrdev(MAJOR_NUM, \"globalvar\", &globalvar_fops); if (ret) {

printk(\"globalvar register failure\"); } else {

printk(\"globalvar register succe\"); } return ret; } static void __exit globalvar_exit(void) { int ret; //注销设备驱动

ret = unregister_chrdev(MAJOR_NUM, \"globalvar\");

if (ret) {

printk(\"globalvar unregister failure\"); } else {

printk(\"globalvar unregister succe\"); } } static ize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off) { //将global_var从内核空间复制到用户空间

if (copy_to_user(buf, &global_var, sizeof(int))) {

returnEFAULT; } return sizeof(int); } module_init(globalvar_init); module_exit(globalvar_exit); //makefile 同上面的hello world,只需修改hello.o为globalvar.o便可以。 接着我们创建设备节点,用户进程通过/dev/globalvar这个路径就可以访问到这个全局变量虚拟设备了。: mknod /dev/globalvar c 254 0 我们写一个用户态的程序globalvartest.c来验证上述设备: #include #include #include #include main() { int fd, num; //打开\"/dev/globalvar\" fd = open(\"/dev/globalvar\", O_RDWR, S_IRUSR | S_IWUSR); if (fd != -1 ) {

//初次读globalvar

read(fd, &num, sizeof(int));

printf(\"The globalvar is %d\\n\", num);

//写globalvar printf(\"Please input the num written to globalvar\\n\"); scanf(\"%d\", &num); write(fd, &num, sizeof(int));

//再次读globalvar

read(fd, &num, sizeof(int));

printf(\"The globalvar is %d\\n\", num);

//关闭\"/dev/globalvar\" close(fd); } else {

printf(\"Device open failure\\n\"); } }

1、引言

记得在学习VC++和C语言的时候,一开始都会以一个HELLO WORLD的例子作为演示,将学者逐渐引入殿堂,这个几乎成了计算机编程语言学习必经的一个入门之路。

当然,在学习linux编程的时候也是这样,下面的例子应该是再熟悉不过了:

首先用VI编写一个C程序:vi hello.c #include \"stdio.h\" int main() { printf(\"hello world!!!\\n\"); return 0; } 接着用GCC进行编译:gcc -o hello hello.c 最后运行该程序:./hello 在终端上你会看到:hello world!!! 上面的是在操作系统基础上进行的用户应用程序的开发。然而对于linux驱动程序的开发是绝然不同的,因为驱动程序的开发是运行在内核空间的,而应用程序是运行在用户空间的。虽然hello world是一个简单得不能再简单的程序,但是对于嵌入式linux驱动程序的初学者来说,通过这个过程的操作可以对linux驱动程序开发的过程和其中的一些概念有一个深刻的认识。所以,我在这里也就以前学习linux的基础上整理了一下,写了这篇博客。一方面是自己对这方面知识的回顾和巩固,另一方面更是希望这里的内容能给大家提供那么一点点有用的信息,小弟心里就很高兴了。当然希望有高手可以做下评价和指导,及时纠正小弟的错误,谢谢先。

2、概念

驱动程序作为系统内核的一部分,它工作在核心态,而应用程序工作在用户态。也就是说,程序不能直接通过指针,把用户空间的数据地址传递给内核(因为MMU映射的地址根本不一样)。要想在应用程序和驱动程序之间传递数据(指针),就需要经过转换。把用户态“看到”的空间地址转换成内核态可访问的地址。Linux系统提供了一系列方便的函数实现这种转换,如get_user、put_user、copy_from_user、copy_to_user等,它们自己负责访问权限的检查,使用时,不需要关系更多的问题。

Linux内核把驱动程序划分为3种类型:字符设备、块设备和网络设备。字符设备和块设备可以像文件一样被访问。它们的主要区别不在于能否seek,而是在于系统对于这两种类型设备的管理方式。应用程序对于字符设备的每一个I/O操作,都会直接传递给系统内核对应的驱动程序;而应用程序对于块设备的操作,要经过系统的缓冲区管理,间接传递给驱动程序处理。块设备的这种管理方式是为存储提供优化的;而字符设备的管理方式是为操作提供优化的。至于网络设备,它在Linux系统中是一类比较特殊的设备它不像字符设备或块设备那样通过对应的设备文件节点去访问,内核也不再通过read和write等调用去访问网络设备。Linux的网络系统主要是基于BSD UNIX的套接字机制,在系统和驱动程序之间有专门的数据结构进行数据传输,系统支持对数据发送和数据接收缓存,提供流量控制机制,提供更多的协议支持。

在linux系统中,驱动程序都做成模块的形式,也就是module。简单的说,一个模块提供一个功能,这些模块是可以按照需要随时装入内核空间和从内核空间卸载的。因此,内核模块是为了给内核动态增减功能而设计的,并不仅仅是限于驱动程序。

关于内核模块初始化(加载)函数

当用户输入命令“insmod 模块文件名”(或者其他方式)加载内核模块时,系统会检测此模块能否被加载,如果能被加载,内核调用模块的初始化函数。在linux 2.4中,内核模块的初始化函数名为init_module()。但如果驱动程序需要编译进内核,则初始化函数不能与内核的其他部分(包括其他内核模块)的函数同名。这样,如果程序可能编译到内核中时就比较麻烦。不过在这个头文件中定义了宏module_init(),用来屏蔽两者的差别,事实上,使用这个宏可以使驱动程序向上兼容linux 2.6,而兼容linux 2.0也比较方便。

关于内核模块清除(卸载)函数

当用户输入命令“rmmod 模块文件名”(或者其他方式)卸载内核模块时,此时,系统会检测此模块是否能被卸载,内核将调用模块清除函数。在linux 2.4中,清除函数的函数名称为cleanup_module()。但如果驱动程序需要编译进内核,则初始化函数不能与内核的其他部分(包括其他内核模块)的函数同名。这样,如果程序可能编译到内核中时就比较麻烦。不过在这个头文件中定义了宏module_exit(),用来屏蔽两者的差别。

3、实例

因为内核模块需要加载到内核空间,所以其程序的编写与一般应用程序不同,在里面再也找不到类似main()这样的入口函数,下面对应函数相应的源代码hello.c,介绍一个驱动模块的写法。(由于尖括号不能在博客上显示,固用双括号代替,在编程调试的时候换回来就可以了)。

#ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif

#include 《linux/module.h》 //所有模块都需要的头文件 #include 《linux/sched.h》 #include 《linux/kernel.h》

#include 《linux/init.h》 // init和exit相关宏

MODULE_LICENSE(\"GPL\");

int text_init(void){ printk(\"Hello World!\"); return 0; } void text_cleanup(void){ printk(\"Goodbye World!\"); }

module_init(text_init); //注册加载时执行的函数 module_exit(text_cleanup); //注册卸载时执行的函数

、调试

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

程序中的MODULE_LICENSE(\"GPL\")用于声明模块的许可证。

编译:

gcc -c -I /usr/src/linux-2.4/include/ hello.c 运行:

insmod hello.o 终端上会显示:

localhost kernel:Hello World! 同时在/proc/modules里面会看到相应的设备信息:

more /proc/modules 你会看到(或许后面的数值不一样):

hello 844 0 (unused) ....... 卸载驱动程序:

rmmod hello 你将会在终端上面看到:

localhost kernel:Goodbye World!

5、注意:

在gcc编译选项中增加-c 在gcc编译选项中定义两个宏:-DMODULE -D__KERENL__ 或直接在源文件中定义这两个宏:

#define MODULE #define __KERNEL__ 在源文件中包括module.h文件:

#include \"linux/module.h\"

假定你现在运行的内核的源码目录绝对路径是MyKernelSrcPath,在gcc编译时增加选项:

-I $MyKernelSrcPath/include (如-I /usr/src/linux/include) 某些时候用insmod -f能够成功加载,但需谨慎使用。 如果看不到用printk打印的信息,可以用dmesg命令看。 打印消息受级别的限制,消息级别可以通过printk设置,如: printk(\" 《n》something\"); /* 其中0

另一方面可以改变控制台的消息级别(可从1到8),如改为8可用以下命令: # echo \"8\" > /proc/sys/kernel/printk

嵌入式linu学习心得

嵌入式linux驱动学习总结

嵌入式软件工程师底层驱动内核工程师

基于嵌入式Linux的设备驱动程序设计

基于嵌入式Linux的IPMI驱动程序设计

嵌入式Linux底层驱动软件工程师岗位职责

基于嵌入式ARMLinux无线ZigBee协调器驱动设计.

嵌入式Linux下3G模块的驱动和应用

嵌入式

如何在嵌入式LINUX中增加自己的设备驱动

嵌入式Linux驱动
《嵌入式Linux驱动.doc》
将本文的Word文档下载到电脑,方便编辑。
推荐度:
点击下载文档
点击下载本文文档