作者:宋宝华 email:author@linuxdriver.cn

在过去这些年,Linux已经成功应用于服务器和桌面系统,而近年来,随着嵌入式系统应用的持续升温,Linux也开始广泛应用于嵌入式领域,逐步成为通信、工业控制、消费电子等领域的主流操作系统。Linux正以其独特的优势极大地吸引电子设计工程师,很多工程师从自己编写的或专用的RTOS转移到Linux,Linux在嵌入式系统中的占有率与日俱增。全世界有无数的嵌入式产品正使用Linux作为其操作系统,在这些采用Linux作为操作系统的设备中,无一例外都包含着多个Linux设备驱动,没有这些设备驱动,用户便无法享受Linux上诸多精彩纷呈的应用。
1.Linux设备驱动开发的基础
    Linux设备驱动的开发需要牢固的硬件基础,并需要对驱动中所涉及的Linux内核知识有良好的掌握,具体表现在:
    (1)驱动直接与硬件打交道,在编写某类硬件设备的驱动时,我们必须对该驱动涉及到的硬件的工作原理和接口有清楚的掌握,因为许多时候,我们需要直接操作寄存器、控制中断和DMA。
    (2)编写Linux设备驱动涉及到许多Linux内核的API,会大量使用自旋锁、信号量、等待队列、tasklet、内存与I/O访问,如果对内核中的相关API了解不够充分,很难写出高质量的驱动。
    在Linux设备驱动开发中,自旋锁和信号量是两种最常用的用于并发控制的手段,几乎所有的设备驱动中都使用了自旋锁或信号量。自旋锁和信号量控制临界区的方法相似:
spin_lock (&lock) ; //获取自旋锁,保护临界区
critical section //临界区
spin_unlock (&lock) ; //释放自旋锁

down(&mount_sem);//获取信号量,保护临界区
critical section //临界区
up(&mount_sem);//释放信号量
    自旋锁或信号量的区别在于:信号量是进程级的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的。如果竞争失败,会发生进程上下文切换(当前进程进入睡眠状态,CPU运行其它进程)。当所要保护的临界区访问时间比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。自旋锁锁定期间不允许阻塞,因此要求锁定的临界区小。
    阻塞和非阻塞I/O是设备访问的两种不同模式,阻塞操作意味着在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作,被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。应用程序多以阻塞方式访问设备,在Linux驱动程序中,经常使用等待队列(wait queue)来实现进程的阻塞与唤醒控制,一个典型的流程如下所示:
1  static ssize_t xxx_write(struct file *file, const char *buffer, size_t count,
2    loff_t *ppos)
3  {
4    ...
5    DECLARE_WAITQUEUE(wait, current); //定义等待队列
6    add_wait_queue(&xxx_wait, &wait);  //添加等待队列

8    ret = count;
9    /* 等待设备缓冲区可写 */
10   do
11   {
12     avail = device_writable(...);
13     if (avail < 0)
14       __set_current_state(TASK_INTERRUPTIBLE);//改变进程状态
15
16     if (avail < 0)
17     {
18       if (file->f_flags &O_NONBLOCK)  //非阻塞
19       {
20         if (!ret)
21           ret =  - EAGAIN;
22         goto out;
23       }
24       schedule();   //调度其他进程执行
25       if (signal_pending(current))//如果是因为信号唤醒
26       {
27         if (!ret)
28           ret =  - ERESTARTSYS;
29         goto out;
30       }
31     }
32   }while (avail < 0);
33
34   /* 写设备缓冲区 */
35   device_write(...)
36   out:
37   remove_wait_queue(&xxx_wait, &wait);//将等待队列移出等待队列头
38   set_current_state(TASK_RUNNING);//设置进程状态为TASK_RUNNING
39   return ret;
40 }
    上述流程中,当设备暂时不可写时,驱动主动通过schedule()调度其他进程执行本身进入睡眠状态,由于进程进入被加入了等待队列,它可以被中断或其他执行路径唤醒。
    大多数外设都包含一个以上的中断,Linux将中断分成了2个半部,即顶半部和底半部,顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求,而中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。底半部可以被新的中断打断,这也是底半部和顶半部的最大不同。tasklet、work-queue是Linux内核中常用的用于调度底半部执行的机制,调度底半部的典型方法如下:
1  /*定义tasklet和底半部函数并关联*/
2  void xxx_do_tasklet(unsigned long);
3  DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);

5  /*中断处理底半部*/
6  void xxx_do_tasklet(unsigned long)
7  {
8      ...
9  }
10
11 /*中断处理顶半部*/
12 irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
13 {
14     ...
15     tasklet_schedule(&xxx_tasklet); //调度底半部执行
16     ...
17 }
    高性能处理器一般会提供一个内存管理单元(MMU),该单元辅助操作系统进行内存管理,提供虚拟地址和物理地址的映射、内存访问权限保护和CACHE缓存控制等硬件支持。Linux的每个进程可以访问4GB的内存,0~3GB位于用户空间,对所有进程单独控制,3GB~4GB位于内核空间,被所有进程共享。在Linux内核空间申请内存涉及到的函数主要包括kmalloc()、__get_free_pages()和vmalloc()等。kmalloc()和__get_free_pages()申请的内存在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系。而vmalloc() 在虚拟内存空间给出一块连续的内存区,实质上,这片连续的虚拟内存在物理内存中并不一定连续,而vmalloc()申请的虚拟内存和物理内存之间也没有简单的换算关系。
    在驱动中,对于外设的寄存器,不能直接访问物理地址,需访问经过映射后的虚拟地址。外设的寄存器可以用2种方式被映射到虚拟地址,一是静态映射,二是通过ioremap()动态映射。静态映射的方法是在将Linux移植到特定平台时建立一个 map_desc数组,通过 MACHINE_START和MACHINE_END宏之间的.map_io成员函数建立页面。       
2.Linux设备驱动的架构
    近年来内核在驱动方面更偏向于提供设备驱动的架构(Framework)而非单个设备驱动,考虑到框架更强的兼容性,字符设备、块设备、网络设备、MTD设备、TTY设备、I2C设备、LCD设备、音频设备、摄像头、USB设备、PCI设备等驱动的体系结构都变得愈发复杂。
    Linux设备驱动首先作为一个内核模块而存在,模块可以直接编译进内核或编译为.ko文件通过insmod、modprobe动态加载。
    Linux字符设备驱动的核心是file_operations结构体,驱动的主体是实现其中的read()、write()、ioctl()等成员函数,如:
1 struct file_operations xxx_fops =
2 {
3   .owner = THIS_MODULE,
4   .read = xxx_read,
5   .write = xxx_write,
6   .ioctl = xxx_ioctl,
7   ...
8 };
    Linux块设备驱动并不直接实现file_operations成员函数,其主体变成处理实现block_device_operations成员函数以及处理上层下达的I/O请求,处理I/O请求典型流程如下:
1  static void xxx_request(request_queue_t *q)
2  {
3    struct request *req;
4    while ((req = elv_next_request(q)) != NULL)
5    {
6      struct xxx_dev *dev = req->rq_disk->private_data;
7      if (!blk_fs_request(req)) //不是文件系统请求
8      {
9        printk(KERN_NOTICE "Skip non-fs request\n");
10       end_request(req, 0);//通知请求处理失败
11       continue;
12     }
13     xxx_transfer(dev, req->sector, req->current_nr_sectors, req->buffer,
14       rq_data_dir(req)); //处理这个请求
15     end_request(req, 1); //通知成功完成这个请求
16   }

Linux网络设备驱动的结构如上图所示,其中设备驱动功能层各函数是网络设备接口层net_device 数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。sk_buff 结构体用于表示描述网络包,它定义了对应于传输层TCP/UDP(及ICMP 和IGMP)、网络层 和和链路层协议的协议头。
    Linux下编写网络设备驱动的主体工作是完成net_device结构体的填充以及成员函数的实现,底层最核心的工作是:发送数据包和接收数据包,接收数据包是由中断触发的。发送数据包函数的典型结构如下:
1 int xxx_tx(struct sk_buff *skb, struct net_device *dev)
2 {
3     int len;
4     char *data, shortpkt[ETH_ZLEN];
5     /* 获得有效数据指针和长度 */
6     data = skb->data;
7     len = skb->len;
8     if (len < ETH_ZLEN)
9     {
10          /* 如果帧长小于以太帧最小长度,补0 */
11          memset(shortpkt, 0, ETH_ZLEN);
12          memcpy(shortpkt, skb->data, skb->len);
13          len = ETH_ZLEN;
14          data = shortpkt;
15     }
16
17     dev->trans_start = jiffies; /* 记录发送时间戳 */
18
19     /* 设置硬件寄存器让硬件把数据包发送出去 */
20     xxx_hw_tx(data, len, dev);
21     ...
22 }
    接收数据包的典型结构是:
1 static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
2 {
3     ...
4     switch (status &ISQ_EVENT_MASK)
5     {
6          case ISQ_RECEIVER_EVENT:
7          /* 获取数据包 */
8          xxx_rx(dev);
9          break;
10     /* 其他类型的中断 */
11     }
12 }
13 static void xxx_rx(struct xxx_device *dev)
14 {
15     ...
16     length = get_rev_len (...);
17     /* 分配新的套接字缓冲区 */
18     skb = dev_alloc_skb(length + 2);
19
20     skb_reserve(skb, 2); /* 对齐 */
21     skb->dev = dev;
22
23     /* 读取硬件上接收到的数据 */
24     insw(ioaddr + RX_FRAME_PORT, skb_put(skb, length), length >> 1);
25     if (length &1)
26     skb->data[length - 1] = inw(ioaddr + RX_FRAME_PORT);
27
28     /* 获取上层协议类型 */
29     skb->protocol = eth_type_trans(skb, dev);
30
31     /*把数据包交给上层 */
32     netif_rx(skb);
33
34     /* 记录接收时间戳 */
35     dev->last_rx = jiffies;
36     ...
37 }
    对于其他如MTD设备、TTY设备、I2C设备、LCD设备、音频设备、摄像头、USB设备、PCI设备等,Linux都定义了类似于网络设备驱动的复杂的层次结构,如TTY设备驱动的层次如下:

这些复杂结构的定义,加大了Linux驱动的开发门槛,同时也使得开发Linux驱动甚至具有了类似于使用VC++开发MFC程序的特点。
3.总结
    简言之,可以得出如下等式:Linux设备驱动开发=硬件控制+Linux内核API(用于并发/同步控制、阻塞/唤醒、中断底半部调度、内存和I/O访问等)+驱动框架。等式右边的3个要素缺一不可,开发高质量的Linux驱动也势必要求工程师对这些知识有良好的掌握,拙著《Linux设备驱动开发详解》一书对这些知识都进行了深入讲解。

转载于:https://blog.51cto.com/21cnbao/120012

Linux设备驱动开发概述相关推荐

  1. Linux设备驱动开发基础

    1.驱动概述和开发环境搭建 1.1驱动设备的作用 对设备驱动最通俗的解释就是"驱动硬件设备行动".驱动与底层硬件直接打交道,按照硬件设备的具体工作方式,读写设备的寄存器,完成设备的 ...

  2. 《Linux设备驱动开发详解(第2版)》隆重出版

    Linux设备驱动开发详解(第2版)(前一版狂销3万册,畅销书最新升级) [新品] 点击看大图     基本信息 * 作者: 宋宝华       * 出版社:人民邮电出版社     * ISBN:97 ...

  3. 嵌入式linux设备驱动开发,嵌入式Linux设备驱动开发简介.pdf

    清远见--嵌入式培训专家 http :// "黑色经典"系列之<嵌入式Linux 应用程序开发详解> 11 章 嵌入式Linux 设备驱动开发 本章目标 本书从 6 章 ...

  4. linux 设备驱动 ppt,LINUX设备驱动开发3.ppt

    <LINUX设备驱动开发3.ppt>由会员分享,可在线阅读,更多相关<LINUX设备驱动开发3.ppt(30页珍藏版)>请在人人文库网上搜索. 1.第三天课程,FLASH设备驱 ...

  5. linux设备驱动开发详解孔夫子,Linux设备驱动开发详解

    [内容简介] <Linux设备驱动开发详解(第2版)>是一本介绍linux设备驱动开发理论.框架与实例的书,<Linux设备驱动开发详解(第2版)>基于ldd6410开发板,以 ...

  6. linux设备驱动总结,《Linux设备驱动开发详解(第3版)》海量更新总结

    本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 2015.2.26 几乎完成初稿. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第1章 <Linux ...

  7. 《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)进展同步更新

    本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 目前已经完成稿件. 2015年8月9日,china-pub开始上线预售: http://product.china-pub ...

  8. 《Linux设备驱动开发详解 A》一一2.3 接口与总线

    本节书摘来华章计算机出版社<Linux设备驱动开发详解 A>一书中的第2章,第2.3节,作者:宋宝华 更多章节内容可以访问云栖社区"华章计算机"公众号查看.1 2.3 ...

  9. linux设备驱动开发详解源码,linux设备驱动开发详解光盘源码.rar

    压缩包 : linux设备驱动开发详解光盘源码.rar 列表 19/busybox源代码/busybox-1.2.1.tar.bz2 19/MTD工具/mtd-utils-1.0.0.tar.gz 1 ...

最新文章

  1. 什么是虚拟专用网(×××)
  2. 图像的膨胀与腐蚀、细化
  3. python基本程序结构有几种_python基础梳理(一)(推荐)
  4. Wireshark数据包分析(一)——使用入门
  5. 索尼PS5将配备超快速的存储设备:最快2020年2月亮相
  6. 基于文本和语音的双模态情感分析
  7. 【Android】3.24 示例24--OpenGL绘制功能
  8. 网络负载平衡(Network Load Balancing)的工作原理
  9. css-布局1-基本属性
  10. wordpress 表格文字对齐_掌握Word对齐技巧,排版又快又美观
  11. 百度商业推广php,百度“知心搜索”,背后商业协议
  12. 详细分析小米抢购软件的实现步骤
  13. Mac实用软件及功能总结
  14. Git和Cmake下载超级慢的解决方案
  15. Visual studio插件:Visual Svn 替代工具--AnkhSvn
  16. [IT最前沿--有点意思] 微博营销案例:杜蕾斯鞋套雨夜传奇
  17. 第二章使用VLAN隔离广播域
  18. 百善孝为先,万恶淫为首
  19. Windows下运行Makefile
  20. 非网页版微信机器人-Wechaty

热门文章

  1. 2022版全球及中国煤矿机械再制造行业“十四五”发展规划与投资建议报告
  2. 全球及中国橡胶行业盈利状况及十四五竞争前景展望报告2021版
  3. VC6命令行编译工程方法
  4. c#之Redis实践list,hashtable
  5. switch case 判断是否为按钮、设置属性 Load Foreach 绑定事件
  6. Spring学习总结(一)——Spring实现IoC的多种方式
  7. ajax传值到ashx接收反序列
  8. IIS Form 认证 保护HTML页面
  9. SAP 全球产品营销总监:产品营销驱动 B2B 企业爆发式增长的 6 大要点
  10. 2018 Multi-University Training Contest 7 - Sequence