Linux系统驱动开发

Linux系统驱动开发

1:Linux设备通常划分为三种:字符设备、块设备和网络接口设备。

字符设备是指:那些只能一个字节一个字节读写数据的设备,不能随即读取设备内存中的某一数据。其读取数据需要按照先后顺序,从这点上看,字符设备是面向数据流的设备。常用的字符设备有鼠标、键盘、串口、控制台和LED等设备。

块设备是指:可以从设备的任意位置读取一定长度数据的设备。其读取数据不必按照先后的顺序,可以定位到设备的某一具体的位置,读取数据。常见的块设备有硬盘,磁盘,U盘和SD卡。

每一个字符设备或者块设备都在/dev目录下对应一个设备文件。进入/dev目录下,执行ls–l命令,以C开头的是字符设备,以b开头的是块设备。

2:主设备号和次设备号

一个字符设备或者一个块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。此设备号用来表示使用该驱动程序的各设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2.这里次设备号为别表示两个LED灯。

2.1主设备号和次设备号的表示:

在linux内核中,设备号用dev_t类型来表示。在linux2.6.29.4中,dev_t定义为一个无符号长整型变量,如下:

typedefu_longdev_t;

u_long在32位机中占4个字节,在64位机中占8个字节,以32位机为例,其中高12位表示主设备号,低20位表示次设备号。

2.2动态分配设备号和静态分配设备号

静态分配设备号,就是驱动程序开发者静态的指定一个设备号。对于一部分成用的设备,内核开发者已经为其分配了设备号,这些设备号可以在内核源码documentation/divice.txt文件中找到,如果只有开发者自己使用这些设备驱动程序,那么可以选择一个尚未使用的一个设备号。在不添加新硬件的时候,这种方式不会产生设备号冲突的问题。但是当添加新硬件的时候,则很可能造成设备号冲突,影响设备的使用。

动态分配设备号,避免了设备号冲突的问题,该函数是alloc_chrdev_region().

2.3查看设备号

当静态分配设备号的时候,需要查看系统中已经存在的设备号,从而决定使用哪个新设备号,可以读取/proc/devices文件获得设备的设备号,该文件中包含字符设备和块设备的设备号。

3:申请和释放设备号

3.1:申请设备号

在构建字符设备之前,首先要向系统申请一个或者多个设备号。完成该工作的函数是register_chrdev_region(),该函数在<fs/char_dev.c>中定义。

intregister_chrdev_region(dev_tfrom,unsignedcount,constchar*name);

其中,from是要分配的设备号的起始值,一般只提供from的主设备号,from的此设备号通常被设置成0.Count是需要申请连续设备号的个数。Name是和该范围编号关联的设备名称,该名称不能超过64个字节。

register_chrdev_region()函数成功执行返回值为0,错误时,返回一个负的错误码,并且不能为字符设备分配设备号。

在linux中有非常多的字符设备,在人为的为字符设备分配设备号时,很可能发生冲突,linux内核开发者一直在努力的将设备号变为动态的。可以使用alloc_chrdev_region()达到这个目的。

intalloc_chrdev_region(dev_t*dev,unsignedbaseminor,unsignedcount,constchar*name);

在上面的函数中,dev作为输出参数,在函数成功返回后将保存已经分配好的设备号,函数有可能申请一段连续的设备号,这是dev返回第一个设备号。Baseminor表示要申请的第一个次设备号,其通常设为0.

3.2:释放设备号

以上两种方式申请的设备号,都应该在不使用的时候释放设备号,设备号的统一释放使用下面的函数:

voidunregister_chrdev_region(dev_tfrom,unsignedcount);

这个函数中的from是要释放的设备号,count表示从form开始要释放的设备号的个数。通常在模块卸载的函数中,调用unregister_chrdev_region()函数。

4:字符设备结构体cdev

Structcdev

{

Structkobjectkobj;/*用于内核管理字符设备,驱动开发人员一般不适用该成员*/

Structmodule*owner;/*指向包含该结构的模块的指针*/

Conststructfile_operations*ops;/*指向字符设备操作函数的指针*/

Structlist_headlist;/*该结构体将使用该驱动的字符设备连成一个结构体,广泛应用,需要掌握*/

Dev_tdev;/*该字符设备的起始设备号,一个设备可能有多个设备号*/

Unsignedintcount;/*使用该字符设备驱动的设备数量*/

}

5file_operatins结构体和其成员函数

staticconststructfile_operationsxxx_fops

{

.owner=THIS_MODULE,/*模块引用,任何时候都赋值THIS_MODULE*/

.read=xxx_read,/*指定设备的读函数*/

.write=xxx_write,/*指定设备的写函数*/

.ioctol=xxx_ioctol,/*指定设备的控制函数*/

};

/*读函数*/

staticssize_txxx_read(structfile*filp,char__user*buf,ssize_tsize,loff_t*opps)

{

If(size>8)

Copy_to_user(buf,…,…);/*当数据较大时,使用copy_to_user(),效率较高;

Else

put_user(…,buf);/*当数据较小时,使用put_user(),效率较高。

}

/*写函数*/

Staticssize_txxx_write(structfile*filp,char__user*buf,ssize_tsize,loff_t*opps)

{

If(size>8)

Copy_from_user(buf,…,…);/*当数据较大时,使用copy_form_user(),效率较高;

else

get_user(…,buf);/*当数据较小时,使用get_user(),效率较高。

}

/*seek文件定位函数*/

staticloff_tVirtualDisk_llseek(structfile*filp,loff_toffset,intorig)

{

loff_tret=0;/*返回的位置偏移*/

switch(orig)

{

caseSEEK_SET:/*相对文件开始位置偏移*/

if(offset<0)/*offset不合法*/

{

ret=-EINVAL;/*无效的指针*/

break;

}

if((unsignedint)offset>VIRTUALDISK_SIZE)

/*偏移大于设备内存*/

{

ret=-EINVAL;/*无效的指针*/

break;

}

filp->f_pos=(unsignedint)offset;/*更新文件指针位置*/

ret=filp->f_pos;/*返回的位置偏移*/

break;

caseSEEK_CUR:/*相对文件当前位置偏移*/

if((filp->f_pos+offset)>VIRTUALDISK_SIZE)

/*偏移大于设备内存*/

{

ret=-EINVAL;/*无效的指针*/

break;

}

if((filp->f_pos+offset)<0)/*指针不合法*/

{

ret=-EINVAL;/*无效的指针*/

break;

}

filp->f_pos+=offset;/*更新文件指针位置*/

ret=filp->f_pos;/*返回的位置偏移*/

break;

default:

ret=-EINVAL;/*无效的指针*/

break;

}

returnret;

}

/*ioctol设备控制函数*/

staticlongioctol(staticfile*file,unsignedintcmd,unsignedlongarg)

{

Switch(cmd)

{

Casexxx_cmd1:

…/*命令1执行的操作*/

Break;

Casexxx_cmd2:

…/*命令2执行的操作*/

Break;

Default:

Return-EINVAL;/*内核和驱动程序都不支持该命令时,返回无效的命令*/

}

Return0;

}

7驱动程序与应用程序的数据交换

驱动程序和应用程序的数据交换是非常重要的。

File_operations中的read()和write()函数就是用来在驱动程序和应用程序间进行数据交换的。通过数据交换,驱动程序和应用程序可以彼此了解对方。但是驱动程序和应用程序属于不同的地址空间,驱动程序不能直接访问应用程序的地址空间;同样,应用程序也不能直接访问驱动程序的地址空间,否则会破坏彼此空间的数据,从而造成系统崩溃,或者数据紊乱。

安全的方法是:使用内核提供的专用函数,完成数据在应用程序空间和驱动程序空间的交换。这些函数对用户程序传过来的指针进行了严格的检查和必要的转换,从而保证了用户程序和驱动程序数据交换的安全性。这些函数有:

unsignedlongcopy_to_user(void__user*to,constvoid*form,unsignedlongn);

unsignedlongcopy_from_user(void__user*to,constvoid*form,unsignedlongn);

put_user();

get_user();

8–字符设备小结:

字符设备的驱动程序完成的主要工作是初始化,添加和删除cdev结构体,申请和释放设备号,以及填充file_operations结构体中的read(),write()ioctl()等重要函数。

可是我知道结果是惨淡的,但还是心存希望!

Linux系统驱动开发

相关文章:

你感兴趣的文章:

标签云: