Chinaunix首页 | 论坛 | 博客
  • 博客访问: 251528
  • 博文数量: 52
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1538
  • 用 户 组: 普通用户
  • 注册时间: 2013-04-24 07:45
个人简介

生活就像海洋,只有意志坚强的人,才能到达彼岸。

文章存档

2013年(52)

分类: LINUX

2013-08-21 10:49:43

一、驱动程序设计介绍

    驱动分类:字符设备驱动(重点)、网络接口驱动(重点)、块设备驱动。

1>字符设备

字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现open、

close、read、write系统调用。

2>块设备

在大部分的Unix系统,块设备不能按字节处理数据,只能一次传送一个活多个长度是512字节(或一

个更大的2次幂的数)的整块数据

Linux则允许块设备传送任意数目的字节。因此,块和字符设备的区别仅仅是驱动的与内核的接口

不同

3>网络接口

任何网络事务都通过一个接口来进行,一个接口通常是一个硬件设备(eth0),但是它也可以是一个

纯粹的软件设备,比如回环接口(lo)。一个网络接口负责发送和接收数据报文。

驱动程序安装

1、内核模块方式 2、直接编译进内核

使用驱动程序

Linux用户程序通过设备文件(又名:设备节点)来使用驱动程序操作字符设备和块设备,设备文件位于dev/目录下。


二、字符设备程序设计:

1>设备号

    字符设备通过字符设备文件来存取。字符设备文件由使用ls -l的输出的第一列‘c’标示。如果

使用ls -l命令,会看到在设备文件项中有2个数(由一个逗号分隔)这些数字就是设备文件的主次设

备编号

    主设备号:用来标识与设备文件相连的驱动程序,用来反应设备类型

    次设备号:被驱动程序用来辨别操作的哪个设备,用来区分同类型的设备。

    主次设备号的描述:dev_t:其实质为unsigned int 32位整数,其中高12位为主设备号,低20位为次设备号。主设备号:MAJOR(dev_t dev),次设备号:MINOR(dev_t dev)

    分配主设备号静态申请、动态分配

    1、静态申请:1、根据Documenttation/devices.txt,确定一个没有使用的主设备号

                 2、使用register_chrdev_region函数注册设备号 。

int register_chrdev_region(dev_t from, unsigned count, const char* name)

功能:申请使用从from开始的count个设备号(主设备号不变,次设备号增加)

参数:
    from:希望申请使用的设备号
    cout:希望申请使用设备号数目
    name:设备名(体现在/proc/devices)
    

    该分配方式优点是简单,其缺点是一旦驱动被广泛使用,这个随机选定的主设备号可能会导致设

备号冲突,而是驱动程序无法注册。

  
    2、动态分配:使用alloc_chrdev_region分配设备号

int alloc_chrdev_region(dev_t* dev, unsigned baseminor, unsigned count, const char* name)

功能:
    请求内核动态分配count个设备号,且次设备号从baseminor开始。

参数
    dev:分配到的设备号
    baseminor:起始次设备号
    count:需要分配到的设备号数目
    name:设备名

该分配方式优点是简单,易于驱动推广,缺点是无法在安装驱动前创建设备文件(因为安装前还没有分配到主设备号)

安装驱动后,从/proc/devices中查询设备号

    注销设备号:不论使用各种方法分配设备号,都应该在不再使用它们时释放这些设备号。

void unregister_chrdev_region(dev_t from,unsigned count)

功能:释放从from开始的coun个设备号

2>创建设备文件

    使用mknod命令手工创建

    mknod filename type major minor

    filename :设备文件名
    type :设备文件类型
    major :主设备号
    minor :次设备号

    自动创建


3>设备注册

在linux2.6内核中,字符设备使用struct cdev来描述。字符设备的注册科分为3个步骤:

1、分配cdev:struct cdev的分配可使用cdev_alloc函数来完成。

            struct cdev* cdev_alloc(void)

2、初始化cdev:struct cdev 的初始化使用cdev_init函数来完成。

        void cdev_init(struct cdev* cdev, const struct file_operations* fops)

参数:
    cdev:待初始化的cdev结构
    fops:设备对应的操作函数集

3、添加cdev:struct cdev 的注册使用cdev_add函数来完成。

        int cdev_add(struct cdev* p,dev_t dev,unsigned count)

参数:
    p:待添加到内核的字符设备结构
    dev:设备号
    count:添加的设备个数

4>重要数据结构

在linux字符设备驱动程序设计中,有3种非常重要的数据结构:

struct file, struct inode, struct file_operations

    struct file:

代表一个打开的文件。系统中每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建,在文件关闭后释放。

重要成员:

loff_t f_pos /*文件读写位置*/

struct file_operations* f_op 

一个函数指针的集合,定义能在设备上进行的操作。结构中的成员指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作保留为NULL 

例:mem_fops

struct file_operations mem_fops = {
    .owner = THIS_MODULE,
    .llseek = mem_seek,
    .read = mem_read,
    .write = mem_write,
    .ioctl = mem_ioctl,
    .open = mem_open,
    .release = mem_release,

};

    struct inode:

用来记录文件的物理上的信息。因此,它和代表打开文件的file结构不同的。一个文件可以对应多个file结构,但只有一个inode结构。

重要成员:
    dev_t i_rdev: 设备号

5>设备操作

1、int(*open)(struct inode*,struct file*)

    在设备文件上的第一个操作,并不要求驱动程序一定要实现这个方法。如果该项为NULL,设备的打开操作永远成功。
    open方法是驱动程序用来为以后的操作完成初始化准备工作的。在大部分驱动程序中,open完成的工作是:初始化设备,标明次设备号

2、void(*release)(struct inode*, struct file*)

    当设备文件被关闭时调用这个操作,与open相仿,release也可以没有。Release方法的作用正好与open相反。这个设备方法有时也称close,它的作用是:关闭设备

3、ssize_t(*read)(struct file*,char __user*,size_t,loff_t*)

    从设备中读取数据。

4、ssize_t(*write)(struct file*,const char __user*,size_t,loff_t*)

    向设备发送数据。

    读和写方法都完成类似的工作:从设备中读取数据到用户空间;将数据传递给驱动程序。他们的原型也相当类似:

ssize_t xxx_read(struct file* filp,char __user* buff,size_t count,loff_t*offp);

ssize_t xxx_read(struct file* filp,char __user* buff,size_t count,loff_t*offp);

对于2个方法,filp是文件指针,count是请求传输的数据量,buff参数指向数据缓存,最后offp之处文件当前的访问位置。

read和write方法的buff参数是用户空间指针。因此,它不能被内核代码直接饮用,理由是用户空间指针在内核空间时可能根本就是无效的——没有那么地址映射。内核提供了专门的函数用于访问用户空间的指针:

    int copy_from_user(void* to,const void __user* from, int n)
    int copy_to_user(void __user* to, const void* from, int n)


5、unsigned int(*poll)(struct file*, struct poll_table_struct*)

    对应select系统调用

6、int(*iotcl)(struct inode*,struct file*,unsigned int,unsigned long)
    
    控制设备
    
7、int(*mmap)(struct file*,struct vm_area_struct*)

    将设备映射到进程虚拟地址空间中。

8、off_t(*llseek)(struct file*, loff_t,int)

    修改文件的当前读写位置,并将新位置作为返回值。

9、int cdev_del(struct cdev* p)

功能:字符设备注销

参数:
    p:要注销的字符设备结构

6>字符设备驱动程序设计范例:

点击(此处)折叠或打开

  1. /*******************************memdev.h********************************/
  2. #ifndef _MEMDEV_H_
  3. #define _MEMDEV_H_

  4. #ifndef MEMDEV_MAJOR
  5. #define MEMDEV_MAJOR 251 /*预设的mem的主设备号*/
  6. #endif

  7. #ifndef MEMDEV_NR_DEVS
  8. #define MEMDEV_NR_DEVS 2 /*设备数*/
  9. #endif

  10. #ifndef MEMDEV_SIZE
  11. #define MEMDEV_SIZE 4096
  12. #endif

  13. /*mem设备描述结构体*/
  14. struct mem_dev
  15. {
  16.   char *data;
  17.   unsigned long size;
  18. };

  19. #endif /* _MEMDEV_H_ */

  20. /*********************memdev.c******************************/
  21. #include <linux/module.h>
  22. #include <linux/types.h>
  23. #include <linux/fs.h>
  24. #include <linux/errno.h>
  25. #include <linux/mm.h>
  26. #include <linux/sched.h>
  27. #include <linux/init.h>
  28. #include <linux/cdev.h>
  29. #include <linux/slab.h>
  30. #include <asm/io.h>
  31. #include <asm/system.h>
  32. #include <asm/uaccess.h>
  33. #include "memdev.h"

  34. static int mem_major = MEMDEV_MAJOR;
  35. module_param(mem_major, int, S_IRUGO);

  36. struct mem_dev *mem_devp; /*设备结构体指针*/
  37. struct cdev cdev;

  38. /*
  39.  * 文件打开函数
  40.  * */
  41. int mem_open(struct inode *inode, struct file *filp)
  42. {
  43.     struct mem_dev *dev;
  44.     /*获取次设备号*/
  45.     int num = MINOR(inode->i_rdev);

  46.     if (num >= MEMDEV_NR_DEVS)
  47.         return -ENODEV;
  48.     dev = &mem_devp[num];
  49.     /*将设备描述结构指针赋值给文件私有数据指针*/
  50.     filp->private_data = dev;

  51.     return 0;
  52. }

  53. /*
  54.  * 文件释放函数
  55.  * */
  56. int mem_release(struct inode *inode, struct file *filp)
  57. {
  58.     return 0;
  59. }

  60. /*
  61.  * 读函数
  62.  * */
  63. static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
  64. {
  65.     unsigned long p = *ppos;
  66.     unsigned int count = size;
  67.     int ret = 0;
  68.     struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/

  69.     /*判断读位置是否有效*/
  70.     if (p >= MEMDEV_SIZE)
  71.         return 0;
  72.     if (count > MEMDEV_SIZE - p)
  73.         count = MEMDEV_SIZE - p;

  74.     /*读数据到用户空间*/
  75.     if (copy_to_user(buf, (void*)(dev->data + p), count)) {
  76.         ret = - EFAULT;
  77.     } else {
  78.         *ppos += count;
  79.         ret = count;
  80.         printk(KERN_INFO "read %d bytes(s) from %ld\n", count, p);
  81.     }
  82.     return ret;
  83. }

  84. /*
  85.  * 写函数
  86.  */
  87. static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
  88. {
  89.     unsigned long p = *ppos;
  90.     unsigned int count = size;
  91.     int ret = 0;
  92.     struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/

  93.     /*分析和获取有效的写长度*/
  94.     if (p >= MEMDEV_SIZE)
  95.         return 0;
  96.     if (count > MEMDEV_SIZE - p)
  97.         count = MEMDEV_SIZE - p;

  98.     /*从用户空间写入数据*/
  99.     if (copy_from_user(dev->data + p, buf, count))
  100.         ret = - EFAULT;
  101.     else {
  102.         *ppos += count;
  103.         ret = count;

  104.         printk(KERN_INFO "written %d bytes(s) from %ld\n", count, p);
  105.     }
  106.     return ret;
  107. }

  108. /*
  109.  * seek文件定位函数
  110.  * */
  111. static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
  112. {
  113.     loff_t newpos;

  114.     switch(whence) {
  115.         case 0: /* SEEK_SET */
  116.             newpos = offset;
  117.             break;
  118.         case 1: /* SEEK_CUR */
  119.             newpos = filp->f_pos + offset;
  120.             break;
  121.         case 2: /* SEEK_END */
  122.             newpos = MEMDEV_SIZE -1 + offset;
  123.             break;
  124.         default: /* can't happen */
  125.             return -EINVAL;
  126.     }
  127.     if ((newpos<0) || (newpos>MEMDEV_SIZE))
  128.         return -EINVAL;
  129.     filp->f_pos = newpos;
  130.     return newpos;
  131. }

  132. /*
  133.  * 文件操作结构体
  134.  * */
  135. static const struct file_operations mem_fops =
  136. {
  137.     .owner = THIS_MODULE,
  138.     .llseek = mem_llseek,
  139.     .read = mem_read,
  140.     .write = mem_write,
  141.     .open = mem_open,
  142.     .release = mem_release,
  143. };

  144. /*设备驱动模块加载函数*/
  145. static int memdev_init(void)
  146. {
  147.     int result;
  148.     int i;

  149.     dev_t devno = MKDEV(mem_major, 0);
  150.     /* 静态申请设备号*/
  151.     if (mem_major)
  152.         result = register_chrdev_region(devno, 2, "memdev");
  153.     else { /* 动态分配设备号 */
  154.         result = alloc_chrdev_region(&devno, 0, 2, "memdev");
  155.         mem_major = MAJOR(devno);
  156.     }

  157.     if (result < 0)
  158.         return result;

  159.     /*初始化cdev结构*/
  160.     cdev_init(&cdev, &mem_fops);
  161.     cdev.owner = THIS_MODULE;

  162.     /* 注册字符设备 */
  163.     cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);

  164.     /* 为设备描述结构分配内存*/
  165.     mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
  166.     if (!mem_devp) /*申请失败*/
  167.     {
  168.         result = - ENOMEM;
  169.         goto fail_malloc;
  170.     }
  171.     memset(mem_devp, 0, sizeof(struct mem_dev));

  172.     /*为设备分配内存*/
  173.     for (i=0; i < MEMDEV_NR_DEVS; i++)
  174.     {
  175.         mem_devp[i].size = MEMDEV_SIZE;
  176.         mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
  177.         memset(mem_devp[i].data, 0, MEMDEV_SIZE);
  178.     }
  179.     return 0;

  180. fail_malloc:
  181.     unregister_chrdev_region(devno, 1);

  182.     return result;
  183. }

  184. /*模块卸载函数*/
  185. static void memdev_exit(void)
  186. {
  187.     cdev_del(&cdev); /*注销设备*/
  188.     kfree(mem_devp); /*释放设备结构体内存*/
  189.     unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
  190. }

  191. MODULE_AUTHOR("");
  192. MODULE_LICENSE("GPL");

  193. module_init(memdev_init);
  194. module_exit(memdev_exit);

  195. /******************app_mem.c************************/

  196. #include <stdio.h>
  197. #include <string.h>

  198. int main()
  199. {
  200. FILE *fp0 = NULL;
  201. char Buf[4096];
  202. /*初始化Buf*/
  203. strcpy(Buf, "Mem is char dev!");
  204. printf("BUF: %s\n",Buf);
  205. /*打开设备文件*/
  206. fp0 = fopen("/dev/memdev0","r+");
  207. if (fp0 == NULL) {
  208. printf("Open Memdev0 Error!\n");
  209. return -1;
  210. }
  211. /*写入设备*/
  212. fwrite(Buf, sizeof(Buf), 1, fp0);
  213. /*重新定位文件位置(思考没有该指令,会有何后果)*/
  214. fseek(fp0, 0, SEEK_SET);
  215. /*清除Buf*/
  216. strcpy(Buf, "Buf is NULL!");
  217. printf("BUF: %s\n",Buf);
  218. /*读出设备*/
  219. fread(Buf, sizeof(Buf), 1, fp0);
  220. /*检测结果*/
  221. printf("BUF: %s\n",Buf);
  222. return 0;
  223. }


三、驱动程序调试技术

1>分类:对于驱动程序设计来说,核心问题之就是如何完成调试。当前常用的驱动调试技术分为:打印调试,调试器调试,查询调试

    打印调试:在驱动程序开发时,printk非常有助于调试。但当正式发行驱动程序时应当去掉这些打印语句。合理的使用printk:

#ifdef PDEBUG
#define PLOG(fmt,args...)printk(KERN_DEBUG"SCULL:"fmt,##args)
#else
#define PLOG(fmt,args...)/*do nothing*/
#endif

Makefile作如下修改
DEBUG = y
ifeq($(DEBUG),y)
DEBFLAGS = -O2 -g -DPDEBUG
else
DEBFLAGS = -O2
endif
CFLAGS+=(DEBFLAGS)

四、并发与竞态

并发:多个执行单元同时被执行。

竞态:并发的执行单元对共享资源(硬件资源和软件上的全局变量)的访问导致的竞争状态。

例:

if(copy_from_user(&(dev->data[pos]),buf,count))
ret = -EFAULT;
goto out;

假设有2个进程试图同时向一个设备的相同位置写入数据,就会造成数据混乱。处理并发的常用技术

是加锁或者互斥,即确保在任何时间只有一个执行单元可以操作共享资源。在Linux内核中主要通

semaphore机制和spin_lock实现。

semaphore信号量:Linux内核的信号量是一样的的,但是它不能在内核之外使用,它是一种睡眠锁

如果有一个任务想要获得已经被占用的信号量时,信号量会将这个进程放入一个等待队里,然后让其

睡眠。当持有信号量的任务将被唤醒,并让其获得信号量。

1>信号量的使用

信号量的实现也是与体系结构相关的,定义在中,struct semaphore类型用来表

示信号量。

1、定义信号量
    struct semaphore sem;

2、初始化信号量
    void sema_init(struct semaphore* sem,int val)
该函数用于初始化设置信号量的初值,它设置信号量sem的值val。

    void init_MUTEX(struct semaphore* sem)
该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1.

    void init_MUTEX_LOCKED(struct semaphore* sem)
该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就出在已锁状态。

定义与初始化的工作可由如下宏异步完成:

    DECLARE_MTEX(name)
定义一个信号量name,并初始化它的值为1.

    DECLARE_MUTEX_LOCKED(name)
定义一个信号量name,但把它的初始值设置为0,即锁在创建时就处在已锁状态。

3、获取信号量
    void down(struct semaphore* sem)
获取信号量sem,可能会导致进程睡眠,因此不能在中断上下文中使用该函数。该函数将把sem的值减

一,如果信号量的只非负,就直接返回,否则调用者将被挂起,知道被的任务释放该信号量才能继续

运行。

    int down_interruptible(struct semaphore* sem)
获取信号量sem。如果洗好量不可用,进程将被置为TASK_INTERRUPTIBLE类型的睡眠状态。该函数由

返回值来区分是正常返回还是被信号中断返回,如果返回0,表示获得信号量正常返回,如果被信号

打断,返回-EINTR.

    down_killable(struct semaphore* sem)
获取信号量sem。如果信号量不可用,进程将被置为TASK_KILLABLE类型的睡眠状态。

注意:
    down()函数现已经不建议使用。建议使用down_killable()或down_interruptible()函数

2>自旋锁

    自旋锁最多只能被一个可执行单元持有。自旋锁不会引起调用者睡眠,如果一个执行线程试图获

得一个已经被持有的自旋锁,那么线程就会一直进行忙循环,一直等待下去,在那里是否应该自旋锁

的保持者已经释放了锁,“自旋”就是这个意思。

spin_lock_init(x)
    该宏用于初始化自旋锁x,自旋锁在使用前必须初始化。

spin_lock(lock)
    获取自旋锁lock,如果成功,立即获得锁,并马上返回,否则它将一直自旋在那里,知道该自旋

的保持者释放。

spin_trylock(lock)
    试图获取自旋锁lock,如果立即获得锁,并返回真,否则立即返回假。它不会一直等待释放。

spin_unlock(lock)
    释放自旋锁lock,它与spin_trylock或spin_lock配对使用。

信号量可能允许多个持有者,而自旋锁在任何时候只能允许一个持有者。当然也有信号量叫互斥信号

量(只能一个持有者),允许多个持有者的信号量叫计数信号量。

信号量适合于保持时间较长的情况;而自旋锁适合于时间非常短的情况。在实际应用中自旋锁控制的

代码只有几行,而持有自旋锁的时间也一般不会超过两次上下文切换的时间,因为线程一旦进行切

换、就至少花费切出切入两次的时间,自旋锁的占用时间如果远远长于两次上下文切换,我们就应该

选择信号量。




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