Chinaunix首页 | 论坛 | 博客
  • 博客访问: 794318
  • 博文数量: 106
  • 博客积分: 1250
  • 博客等级: 少尉
  • 技术积分: 1349
  • 用 户 组: 普通用户
  • 注册时间: 2012-01-09 09:38
文章分类

全部博文(106)

文章存档

2014年(1)

2013年(13)

2012年(92)

分类: LINUX

2012-10-23 20:01:10


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

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

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

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

2主设备号次设备号

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

/*命令执行的操作*/

Break;

Case  xxx_cmd2:

/*命令执行的操作*/

Break;

Default:

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

}

Return  0;

}

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

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

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

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

unsigned  long  copy_to_uservoid  __user * to,  const  void *form,  unsigned long n;

unsigned  long  copy_from_uservoid  __user * to,  const  void *form,  unsigned long n;

put_user()

get_user();

8--字符设备小结:

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

阅读(1486) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~