分类: LINUX
2012-10-23 20:01:10
1:Linux 设备通常划分为三种:字符设备、块设备和网络接口设备。
字符设备是指:那些只能一个字节一个字节读写数据的设备,不能随即读取设备内存中的某一数据。其读取数据需要按照先后顺序,从这点上看,字符设备是面向数据流的设备。常用的字符设备有鼠标、键盘、串口、控制台和LED等设备。
块设备是指:可以从设备的任意位置读取一定长度数据的设备。其读取数据不必按照先后的顺序,可以定位到设备的某一具体的位置,读取数据。常见的块设备有硬盘,磁盘,U盘和SD卡。
每一个字符设备或者块设备都在/dev目录下对应一个设备文件。进入/dev目录下,执行 ls –l 命令,以C开头的是字符设备,以b开头的是块设备。
2:主设备号和次设备号
一个字符设备或者一个块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来表示 一个特定的驱动程序。此设备号用来表示使用该驱动程序的各设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2.这里次设备号为别表示两个LED灯。
2.1 主设备号和次设备号的表示:
在linux内核中,设备号用dev_t类型来表示。在linux 2.6.29.4中,dev_t定义为一个无符号长整型变量,如下:
typedef u_long dev_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(),该函数在
int register_chrdev_region(dev_t from, unsigned count, const char *name);
其中,from是 要分配的设备号的起始值,一般只提供from的主设备号,from的此设备号通常被设置成0. Count是需要申请连续设备号的个数。Name是和该范围编号关联的设备名称,该名称不能超过64个字节。
register_chrdev_region()函数成功执行返回值为0, 错误时,返回一个负的错误码,并且不能为字符设备分配设备号。
在linux中有非常多的字符设备,在人为的为字符设备分配设备号时,很可能发生冲突,linux 内核开发者一直在努力的将设备号变为动态的。可以使用alloc_chrdev_region()达到这个目的。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char*name);
在上面的函数中,dev作为输出参数,在函数成功返回后将保存已经分配好的设备号,函数有可能申请一段连续的设备号,这是dev返回第一个设备号。Baseminor表示要申请的第一个次设备号, 其通常设为0.
3.2 :释放设备号
以上两种方式申请的设备号,都应该在不使用的时候释放设备号,设备号的统一释放使用下面的函数:
void unregister_chrdev_region(dev_t from, unsigned count);
这个函数中的from是要释放的设备号,count 表示从form开始要释放的设备号的个数。通常在模块卸载的函数中,调用unregister_chrdev_region()函数。
4:字符设备结构体cdev
Struct cdev
{
Struct kobject kobj;/*用于内核管理字符设备,驱动开发人员一般不适用该成员*/
Struct module *owner;/*指向包含该结构的模块的指针*/
Const struct file_operations *ops;/*指向字符设备操作函数的指针*/
Struct list_head list;/*该结构体将使用该驱动的字符设备连成一个结构体,广泛应用,需要掌握*/
Dev_t dev;/*该字符设备的起始设备号,一个设备可能有多个设备号*/
Unsigned int count;/*使用该字符设备驱动的设备数量*/
}
5 file_operatins结构体和其成员函数
static const struct file_operations xxx_fops
{
.owner = THIS_MODULE , /*模块引用,任何时候都赋值THIS_MODULE*/
.read = xxx_read, /*指定设备的读函数*/
.write = xxx_write, /*指定设备的写函数*/
.ioctol = xxx_ioctol, /*指定设备的控制函数*/
};
/*读函数*/
static ssize_t xxx_read(struct file * filp, char __user * buf, ssize_t size, loff_t *opps)
{
…
If( size > 8)
Copy_to_user(buf, …, …); /*当数据较大时,使用copy_to_user(), 效率较高;
Else
put_user(…, buf);/*当数据较小时, 使用put_user(),效率较高。
}
/*写函数*/
Static ssize_t xxx_write(struct file * filp, char __user * buf, ssize_t size, loff_t *opps)
{
…
If( size > 8)
Copy_from_user(buf, …, …); /*当数据较大时,使用copy_form_user(), 效率较高;
else
get_user(…, buf); /*当数据较小时, 使用get_user(),效率较高。
}
/* seek文件定位函数 */
static loff_t VirtualDisk_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0; /*返回的位置偏移*/
switch (orig)
{
case SEEK_SET: /*相对文件开始位置偏移*/
if (offset < 0) /*offset不合法*/
{
ret = - EINVAL; /*无效的指针*/
break;
}
if ((unsigned int)offset > VIRTUALDISK_SIZE)
/*偏移大于设备内存*/
{
ret = - EINVAL; /*无效的指针*/
break;
}
filp->f_pos = (unsigned int)offset; /*更新文件指针位置*/
ret = filp->f_pos; /*返回的位置偏移*/
break;
case SEEK_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;
}
return ret;
}
/*ioctol设备控制函数*/
static long ioctol (static file *file, unsigned int cmd, unsigned long arg)
{
…
Switch( cmd )
{
Case xxx_cmd1:
… /*命令1 执行的操作*/
Break;
Case xxx_cmd2:
… /*命令2 执行的操作*/
Break;
Default:
Return -EINVAL;/*内核和驱动程序都不支持该命令时,返回无效的命令*/
}
Return 0;
}
7 驱动程序与应用程序的数据交换
驱动程序和应用程序的数据交换是非常重要的。
File_operations中的read()和write()函数就是用来在驱动程序和应用程序间进行数据交换的。通过数据交换,驱动程序和应用程序可以彼此了解对方。但是驱动程序和应用程序属于不同的地址空间,驱动程序不能直接访问应用程序的地址空间;同样,应用程序也不能直接访问驱动程序的地址空间,否则会破坏彼此空间的数据,从而造成系统崩溃,或者数据紊乱。
安全的方法是:使用内核提供的专用函数,完成数据在应用程序空间和驱动程序空间的交换。这些函数对用户程序传过来的指针进行了严格的检查和必要的转换,从而保证了用户程序和驱动程序数据交换的安全性。这些函数有:
unsigned long copy_to_user(void __user * to, const void *form, unsigned long n);
unsigned long copy_from_user(void __user * to, const void *form, unsigned long n);
put_user();
get_user();
8--字符设备小结:
字符设备的驱动程序完成的主要工作是初始化,添加和删除cdev结构体,申请和释放设备号,以及填充file_operations结构体中的read(),write()ioctl()等重要函数。