深入浅出驱动开发Word格式.docx

上传人:b****8 文档编号:22703984 上传时间:2023-02-05 格式:DOCX 页数:64 大小:259.70KB
下载 相关 举报
深入浅出驱动开发Word格式.docx_第1页
第1页 / 共64页
深入浅出驱动开发Word格式.docx_第2页
第2页 / 共64页
深入浅出驱动开发Word格式.docx_第3页
第3页 / 共64页
深入浅出驱动开发Word格式.docx_第4页
第4页 / 共64页
深入浅出驱动开发Word格式.docx_第5页
第5页 / 共64页
点击查看更多>>
下载资源
资源描述

深入浅出驱动开发Word格式.docx

《深入浅出驱动开发Word格式.docx》由会员分享,可在线阅读,更多相关《深入浅出驱动开发Word格式.docx(64页珍藏版)》请在冰豆网上搜索。

深入浅出驱动开发Word格式.docx

二项(如果本文的例程要在特定的嵌入式系统中运行,还应安装相应的交叉编译器,并包含相应的Linux源代码),如下图:

深入浅出Linux设备驱动编程之内核模块

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

  

(1)直接编译进Linux内核,随同Linux启动时加载;

  

(2)编译成一个可加载和删除的模块,使用insmod加载(modprobe和insmod命令类似,但依赖于相关的配置文件),rmmod删除。

这种方式控制了内核的大小,而模块一旦被插入内核,它就和内核其他部分一样。

  下面我们给出一个内核模块的例子:

#include<

linux/module.h>

//所有模块都需要的头文件

linux/init.h>

//init&

exit相关宏

MODULE_LICENSE("

GPL"

);

staticint__inithello_init(void)

{

 printk("

Hellomoduleinit\n"

 return0;

}

staticvoid__exithello_exit(void)

Hellomoduleexit\n"

module_init(hello_init);

module_exit(hello_exit);

  分析上述程序,发现一个Linux内核模块需包含模块初始化和模块卸载函数,前者在insmod的时候运行,后者在rmmod的时候运行。

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

  程序中的MODULE_LICENSE("

)用于声明模块的许可证。

  

  如果要把上述程序编译为一个运行时加载和删除的模块,则编译命令为:

gcc-D__KERNEL__-DMODULE-DLINUX-I/usr/local/src/linux2.4/include-c-ohello.ohello.c

由此可见,Linux内核模块的编译需要给gcc指示-D__KERNEL__-DMODULE-DLINUX参数。

-I选项跟着Linux内核源代码中Include目录的路径。

 下列命令将可加载hello模块:

insmod./hello.o

下列命令完成相反过程:

rmmodhello

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

我们有必要补充一下Linux内核编程的一些基本知识:

 内存

  在Linux内核模式下,我们不能使用用户态的malloc()和free()函数申请和释放内存。

进行内核编程时,最常用的内存申请和释放函数为在include/linux/kernel.h文件中声明的kmalloc()和kfree(),其原型为:

void*kmalloc(unsignedintlen,intpriority);

voidkfree(void*__ptr);

 kmalloc的priority参数通常设置为GFP_KERNEL,如果在中断服务程序里申请内存则要用GFP_ATOMIC参数,因为使用GFP_KERNEL参数可能会引起睡眠,不能用于非进程上下文中(在中断中是不允许睡眠的)。

  由于内核态和用户态使用不同的内存定义,所以二者之间不能直接访问对方的内存。

而应该使用Linux中的用户和内核态内存交互函数(这些函数在include/asm/uaccess.h中被声明):

unsignedlongcopy_from_user(void*to,constvoid*from,unsignedlongn);

unsignedlongcopy_to_user(void*to,void*from,unsignedlonglen);

copy_from_user、copy_to_user函数返回不能被复制的字节数,因此,如果完全复制成功,返回值为0。

  include/asm/uaccess.h中定义的put_user和get_user用于内核空间和用户空间的单值交互(如char、int、long)。

  这里给出的仅仅是关于内核中内存管理的皮毛,关于Linux内存管理的更多细节知识,我们会在本文第9节《内存与I/O操作》进行更加深入地介绍。

  输出

  在内核编程中,我们不能使用用户态C库函数中的printf()函数输出信息,而只能使用printk()。

但是,内核中printk()函数的设计目的并不是为了和用户交流,它实际上是内核的一种日志机制,用来记录下日志信息或者给出警告提示。

每个printk都会有个优先级,内核一共有8个优先级,它们都有对应的宏定义。

如果未指定优先级,内核会选择默认的优先级DEFAULT_MESSAGE_LOGLEVEL。

如果优先级数字比intconsole_loglevel变量小的话,消息就会打印到控制台上。

如果syslogd和klogd守护进程在运行的话,则不管是否向控制台输出,消息都会被追加进/var/log/messages文件。

klogd只处理内核消息,syslogd处理其他系统消息,比如应用程序。

 模块参数

2.4内核下,include/linux/module.h中定义的宏MODULE_PARM(var,type)用于向模块传递命令行参数。

var为接受参数值的变量名,type为采取如下格式的字符串[min[-max]]{b,h,i,l,s}。

min及max用于表示当参数为数组类型时,允许输入的数组元素的个数范围;

b:

byte;

h:

short;

i:

int;

l:

long;

s:

string。

在装载内核模块时,用户可以向模块传递一些参数:

insmodmodnamevar=value

如果用户未指定参数,var将使用模块内定义的缺省值。

深入浅出Linux设备驱动之字符设备驱动程序

Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得Windows的设备操作犹如文件一般。

在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作,如open()、close()、read()、write()等。

Linux主要将设备分为二类:

字符设备和块设备。

字符设备是指设备发送和接收数据以字符的形式进行;

而块设备则以整个数据缓冲区的形式进行。

字符设备的驱动相对比较简单。

  下面我们来假设一个非常简单的虚拟字符设备:

这个设备中只有一个4个字节的全局变量intglobal_var,而这个设备的名字叫做"

gobalvar"

对"

设备的读写等操作即是对其中全局变量global_var的操作。

  驱动程序是内核的一部分,因此我们需要给其添加模块初始化函数,该函数用来完成对所控设备的初始化工作,并调用register_chrdev()函数注册字符设备:

staticint__initgobalvar_init(void)

 if(register_chrdev(MAJOR_NUM,"

gobalvar"

&

gobalvar_fops))

 {

  //…注册失败

 }

 else

  //…注册成功

  其中,register_chrdev函数中的参数MAJOR_NUM为主设备号,"

为设备名,gobalvar_fops为包含基本函数入口点的结构体,类型为file_operations。

当gobalvar模块被加载时,gobalvar_init被执行,它将调用内核函数register_chrdev,把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户进程对该设备执行系统调用时提供入口地址。

  与模块初始化函数对应的就是模块卸载函数,需要调用register_chrdev()的"

反函数"

unregister_chrdev():

staticvoid__exitgobalvar_exit(void)

 if(unregister_chrdev(MAJOR_NUM,"

))

  //…卸载失败

  //…卸载成功

  随着内核不断增加新的功能,file_operations结构体已逐渐变得越来越大,但是大多数的驱动程序只是利用了其中的一部分。

对于字符设备来说,要提供的主要入口有:

open()、release()、read()、write()、ioctl()、llseek()、poll()等。

  open()函数 对设备特殊文件进行open()系统调用时,将调用驱动程序的open()函数:

int(*open)(structinode*,structfile*);

  其中参数inode为设备特殊文件的inode(索引结点)结构的指针,参数file是指向这一设备的文件结构的指针。

open()的主要任务是确定硬件处在就绪状态、验证次设备号的合法性(次设备号可以用MINOR(inode->

i-rdev)取得)、控制使用设备的进程数、根据执行情况返回状态码(0表示成功,负数表示存在错误)等;

  release()函数 当最后一个打开设备的用户进程执行close()系统调用时,内核将调用驱动程序的release()函数:

void(*release)(structinode*,structfile*);

  release函数的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等。

  read()函数 当对设备特殊文件进行read()系统调用时,将调用驱动程序read()函数:

ssize_t(*read)(structfile*,char*,size_t,loff_t*);

  用来从设备中读取数据。

当该函数指针被赋为NULL值时,将导致read系统调用出错并返回-EINVAL("

Invalidargument,非法参数"

)。

函数返回非负值表示成功读取的字节数(返回值为"

signedsize"

数据类型,通常就是目标平台上的固有整数类型)。

  globalvar_read函数中内核空间与用户空间的内存交互需要借助第2节所介绍的函数:

staticssize_tglobalvar_read(structfile*filp,char*buf,size_tlen,loff_t*off)

 …

 copy_to_user(buf,&

global_var,sizeof(int));

  write()函数 当设备特殊文件进行write()系统调用时,将调用驱动程序的write()函数:

ssize_t(*write)(structfile*,constchar*,size_t,loff_t*);

  向设备发送数据。

如果没有这个函数,write系统调用会向调用程序返回一个-EINVAL。

如果返回值非负,则表示成功写入的字节数。

globalvar_write函数中内核空间与用户空间的内存交互需要借助第2节所介绍的函数:

staticssize_tglobalvar_write(structfile*filp,constchar*buf,size_tlen,loff_t *off)

copy_from_user(&

global_var,buf,sizeof(int));

  ioctl()函数 该函数是特殊的控制函数,可以通过它向设备传递控制信息或从设备取得状态信息,函数原型为:

int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);

  unsignedint参数为设备驱动程序要执行的命令的代码,由用户自定义,unsignedlong参数为相应的命令提供参数,类型可以是整型、指针等。

如果设备不提供ioctl入口点,则对于任何内核未预先定义的请求,ioctl系统调用将返回错误(-ENOTTY,"

Nosuchioctlfordevice,该设备无此ioctl命令"

如果该设备方法返回一个非负值,那么该值会被返回给调用程序以表示调用成功。

  llseek()函数该函数用来修改文件的当前读写位置,并将新位置作为(正的)返回值返回,原型为:

loff_t(*llseek)(structfile*,loff_t,int);

  poll()函数poll方法是poll和select这两个系统调用的后端实现,用来查询设备是否可读或可写,或是否处于某种特殊状态,原型为:

unsignedint(*poll)(structfile*,structpoll_table_struct*);

  我们将在"

设备的阻塞与非阻塞操作"

一节对该函数进行更深入的介绍。

设备"

的驱动程序的这些函数应分别命名为gobalvar_open、gobalvar_release、gobalvar_read、gobalvar_write、gobalvar_ioctl,因此设备"

的基本入口点结构变量gobalvar_fops赋值如下:

structfile_operationsgobalvar_fops={

 read:

gobalvar_read,

 write:

gobalvar_write,

};

  上述代码中对gobalvar_fops的初始化方法并不是标准C所支持的,属于GNU扩展语法。

  完整的globalvar.c文件源代码如下:

linux/fs.h>

asm/uaccess.h>

#defineMAJOR_NUM254//主设备号

staticssize_tglobalvar_read(structfile*,char*,size_t,loff_t*);

staticssize_tglobalvar_write(structfile*,constchar*,size_t,loff_t*);

//初始化字符设备驱动的file_operations结构体

structfile_operationsglobalvar_fops=

globalvar_read,write:

globalvar_write,

staticintglobal_var=0;

//"

globalvar"

设备的全局变量

staticint__initglobalvar_init(void)

 intret;

 //注册设备驱动

 ret=register_chrdev(MAJOR_NUM,"

globalvar_fops);

 if(ret)

  printk("

globalvarregisterfailure"

globalvarregistersuccess"

 returnret;

staticvoid__exitglobalvar_exit(void)

 //注销设备驱动

 ret=unregister_chrdev(MAJOR_NUM,"

globalvarunregisterfailure"

globalvarunregistersuccess"

 //将global_var从内核空间复制到用户空间

 if(copy_to_user(buf,&

global_var,sizeof(int)))

  return-EFAULT;

 }

 returnsizeof(int);

 //将用户空间的数据复制到内核空间的global_var

 if(copy_from_user(&

global_var,buf,sizeof(int)))

module_init(globalvar_init);

module_exit(globalvar_exit);

  运行:

gcc-D__KERNEL__-DMODULE-DLINUX-I/usr/local/src/linux2.4/include-c-oglobalvar.oglobalvar.c

  编译代码,运行:

inmodglobalvar.o

  加载globalvar模块,再运行:

cat/proc/devices

  发现其中多出了"

254globalvar"

一行,如下图:

  接着我们可以运行:

mknod/dev/globalvarc2540

  创建设备节点,用户进程通过/dev/globalvar这个路径就可以访问到这个全局变量虚拟设备了。

我们写一个用户态的程序globalvartest.c来验证上述设备:

sys/types.h>

//访问_LP64和_ILP32的定义,还包含适当时使用的多个基本

派生类型:

clock_t,dev_t,off_t,ptrdiff,size_t,ssize_t,time_t

sys/stat.h>

//获得文件的属性,它可以返回一个结构,里面包含文件全部

属性

stdio.h>

fcntl.h>

//设备驱动程序接口是由结构说明,它定义在fcntl.h中

main()

 intfd,num;

 //打开"

/dev/globalvar"

 fd=open("

O_RDWR,S_IRUSR|S_IWUSR);

//这个函数就是告诉系统,应用程序要以读写的方式打开/dev/globalvar这个设备,然后系统再查找相应的驱动程序内的open函数

打开设备

 if(fd!

=-1)

  //初次读globalvar

  read(fd,&

num,sizeof(int));

  printf("

Theglobalvaris%d\n"

num);

  //写globalvar

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 法律文书 > 辩护词

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1