Linux设备驱动开发详解-Note(2)---设备驱动概述(2)

本文深入探讨Linux设备驱动的实现,以LED驱动为例,从无操作系统环境下的简单实现,到Linux系统下的复杂封装,展示了字符设备、块设备与文件系统交互的过程,并详细解析了Linux驱动与内核接口的使用。

设备驱动概述(2)

成于坚持,败于止步

Linux 设备驱动

设备的分类及特点 

计算机系统的硬件主要由 CPU、存储器和外设组成。随着 IC 制造工艺的发展,目前,芯片的集成度越来越高,往往在 CPU 内部就集成了存储器和外设适配器。ARM、PowerPC、MIPS 等处理器都集成了 UART、I2C 控制器、USB 控制器、SDRAM 控制器等,有的处理器还集成了片内 RAM 和 Flash。 

驱动针对的对象是存储器和外设(包括 CPU 内部集成的存储器和外设),而不是针对 CPU 核。Linux 将存储器和外设分为 3 个基础大类: 

字符设备; 

块设备; 

网络设备。 

字符设备指那些必须以串行顺序依次进行访问的设备,如触摸屏、磁带驱动器、鼠标等。块设备可以用任意顺序进行访问,以块为单位进行操作,如硬盘、软驱等。

字符设备不经过系统的快速缓冲,而块设备经过系统的快速缓冲。但是,字符设备和块设备并没有明显的界限,如 Flash 设备符合块设备的特点,但是我们仍然可以把它作为一个字符设备来访问。 

字符设备和块设备的驱动设计呈现出很大的差异,但是对于用户而言,他们都使用文件系统的操作接口 open()、close()、read()、write()等函数进行访问。 

在 Linux 系统中,网络设备面向数据包的接收和发送而设计,它并不对应于文件系统的节点。内核与网络设备的通信和内核与字符设备、块设备的通信方式完全不同。 另外,TTY 驱动、I2C 驱动、USB 驱动、PCI 驱动、LCD 驱动等本身大体可归纳入 3 个基础大类,但是对于这些复杂的设备,Linux 系统还定义了独特的驱动体系结构。 

Linux 设备驱动与整个软硬件系统的关系 


如图 1.5 所示,除网络设备外,字符设备与块设备都被映射到 Linux 文件系统的文件和目录,通过文件系统的系统调用接口 open()、write()、read()、close()等函数即可访问字符设备和块设备。所有的字符设备和块设备都被统一地呈现给用户。块设备比字符设备复杂,在它上面会首先建立一个磁盘/Flash文件系统,如 FAT、Ext3、YAFFS、JFFS 等。FAT、Ext3、YAFFS、JFFS 规范了文件和目录在存储介质上的组织。
应用程序可以使用 Linux 的系统调用接口编程,也可以使用 C 库函数,出于代码可移植性的考虑,后者更值得推荐。C 库函数本身也通过系统调用接口而实现,如库函数中的 fopen()、fwrite()、fread()、fclose()分别会调用操作系统 API 的 open()、write()、read()、close()函数。

设备驱动的 Hello World:LED 驱动 

无操作系统时的 LED 驱动 

在嵌入式系统的设计中,LED 一般直接由 CPU 的 GPIO(通用可编程 I/O 口)控制。GPIO 一般由两组寄存器控制,即一组控制寄存器和一组数据寄存器。控制寄存器可设置 GPIO 口的工作方式为输入或输出。当引脚被设置为输出时,向数据寄存器的对应位写入 1 和 0 会分别在引脚上产生高电平和低电平;当引脚设置为输入时,读取数据寄存器的对应位可获得引脚上相应的电平信号。 

在本例子中,我们屏蔽具体 CPU 的差异,假设在 GPIO_REG_CTRL 物理地址处的控制寄存器处的第 n 位写入 1 可设置 GPIO 为输出,在 GPIO_REG_DATA 物理地址处的数据寄存器的第 n 位写入 1 或 0 可在引脚上产生高或低电平,则在无操作系统的情况下,设备驱动代码如下:

[html]  view plain copy
  1. 1  #define reg_gpio_ctrl *(volatile int *)(ToVirtual(GPIO_REG_CTRL))   
  2. 2  #define reg_gpio_data *(volatile int *)(ToVirtual(GPIO_REG_DATA))   
  3. 3  /*初始化 LED*/   
  4. 4  void LightInit(void)   
  5. 5  {   
  6. 6    reg_gpio_ctrl |= (1 << n); /*设置 GPIO 为输出*/   
  7. 7  }   
  8. 8     
  9. 9  /*点亮 LED*/   
  10. 10 void LightOn(void)   
  11. 11 {   
  12. 12   reg_gpio_data |= (1 << n); /*在 GPIO 上输出高电平*/   
  13. 13 }   
  14. 14    
  15. 15 /*熄灭 LED*/   
  16. 16 void LightOff(void)   
  17. 17 {   
  18. 18   reg_gpio_data &= ~(1 << n); /*在 GPIO 上输出低电平*/   
  19. 19 }  
上述程序中的 LightInit()、LightOn()、LightOff()等函数都将作为 LED 驱动提供给应用程序的外部接口函数。程序中 ToVirtual()等函数的作用是当系统启动了硬件 MMU之后,根据物理地址和虚拟地址的映射关系,将寄存器的物理地址转化为虚拟地址。 

Linux 系统下的 LED 驱动 

在 Linux 操作系统下编写 LED 设备的驱动时,操作硬件的 LightInit()、LightOn()、LightOff()这些函数仍然需要,但是,需要遵循 Linux 编程的命名习惯,重新将其命名为 light_init()、light_on()、light_off()。这些函数将被 LED 驱动中独立于设备的针对内核的接口进行调用,代码如下给出了 Linux 系统下的 LED 驱动,现在读者并不需要能读懂这些代码。 

[html]  view plain copy
  1. 1   #include .../*包含内核中的多个头文件*/   
  2. 2      
  3. 3   /*设备结构体*/   
  4. 4   struct light_dev   
  5. 5   {   
  6. 6     struct cdev cdev; /*字符设备 cdev 结构体*/   
  7. 7     unsigned char value; /*LED 亮时为 1,熄灭时为 0,用户可读写此值*/   
  8. 8   };   
  9. 9      
  10. 10  struct light_dev *light_devp;   
  11. 11  int light_major = LIGHT_MAJOR;   
  12. 12     
  13. 13  MODULE_AUTHOR("Song Baohua");   
  14. 14  MODULE_LICENSE("Dual BSD/GPL");   
  15. 15     
  16. 16  /*打开和关闭函数*/  
  17. 17  int light_open(struct inode *inode, struct file *filp)   
  18. 18  {   
  19. 19    struct light_dev *dev;   
  20. 20    /* 获得设备结构体指针 */   
  21. 21    dev = container_of(inode->i_cdev, struct light_dev, cdev);   
  22. 22    /* 让设备结构体作为设备的私有信息 */   
  23. 23    filp->private_data = dev;   
  24. 24    return 0;   
  25. 25  }   
  26. 26  int light_release(struct inode *inode, struct file *filp)   
  27. 27  {   
  28. 28    return 0;   
  29. 29  }   
  30. 30     
  31. 31  /*写设备:可以不需要 */   
  32. 32  ssize_t light_read(struct file *filp, char _ _user *buf, size_t count,   
  33. 33    loff_t*f_pos)   
  34. 34  {   
  35. 35    struct light_dev *dev = filp->private_data; /*获得设备结构体 */   
  36. 36     
  37. 37    if (copy_to_user(buf, &(dev->value), 1))   
  38. 38    {   
  39. 39      return  - EFAULT;   
  40. 40    }   
  41. 41    return 1;   
  42. 42  }   
  43. 43     
  44. 44  ssize_t light_write(struct file *filp, const char _ _user *buf, size_t count,   
  45. 45    loff_t *f_pos)   
  46. 46  {   
  47. 47    struct light_dev *dev = filp->private_data;   
  48. 48     
  49. 49    if (copy_from_user(&(dev->value), buf, 1))   
  50. 50    {   
  51. 51      return  - EFAULT;   
  52. 52    }   
  53. 53    /*根据写入的值点亮和熄灭 LED*/   
  54. 54    if (dev->value == 1)  
  55. 55      light_on();   
  56. 56    else   
  57. 57      light_off();   
  58. 58     
  59. 59    return 1;   
  60. 60  }   
  61. 61     
  62. 62  /* ioctl 函数 */   
  63. 63  int light_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,   
  64. 64    unsigned long arg)   
  65. 65  {   
  66. 66    struct light_dev *dev = filp->private_data;   
  67. 67     
  68. 68    switch (cmd)   
  69. 69    {   
  70. 70      case LIGHT_ON:   
  71. 71        dev->value = 1;   
  72. 72        light_on();   
  73. 73        break;   
  74. 74      case LIGHT_OFF:   
  75. 75        dev->value = 0;   
  76. 76        light_off();   
  77. 77        break;   
  78. 78      default:   
  79. 79        /* 不能支持的命令 */   
  80. 80        return  - ENOTTY;   
  81. 81    }   
  82. 82     
  83. 83    return 0;   
  84. 84  }   
  85. 85     
  86. 86  struct file_operations light_fops =   
  87. 87  {   
  88. 88    .owner = THIS_MODULE,   
  89. 89    .read = light_read,   
  90. 90    .write = light_write,   
  91. 91    .ioctl = light_ioctl,   
  92. 92    .open = light_open,   
  93. 93    .release = light_release,  
  94. 94  };   
  95. 95     
  96. 96  /*设置字符设备 cdev 结构体*/   
  97. 97  static void light_setup_cdev(struct light_dev *dev, int index)   
  98. 98  {   
  99. 99    int err, devno = MKDEV(light_major, index);   
  100. 100    
  101. 101   cdev_init(&dev->cdev, &light_fops);   
  102. 102   dev->cdev.owner = THIS_MODULE;   
  103. 103   dev->cdev.ops = &light_fops;   
  104. 104   err = cdev_add(&dev->cdev, devno, 1);   
  105. 105   if (err)   
  106. 106     printk(KERN_NOTICE "Error %d adding LED%d", err, index);   
  107. 107 }   
  108. 108    
  109. 109 /*模块加载函数*/   
  110. 110 int light_init(void)   
  111. 111 {   
  112. 112   int result;   
  113. 113   dev_t dev = MKDEV(light_major, 0);   
  114. 114    
  115. 115   /* 申请字符设备号*/   
  116. 116   if (light_major)   
  117. 117     result = register_chrdev_region(dev, 1, "LED");   
  118. 118   else   
  119. 119   {   
  120. 120     result = alloc_chrdev_region(&dev, 0, 1, "LED");   
  121. 121     light_major = MAJOR(dev);   
  122. 122   }   
  123. 123   if (result < 0)   
  124. 124     return result;   
  125. 125    
  126. 126   /* 分配设备结构体的内存 */   
  127. 127   light_devp = kmalloc(sizeof(struct light_dev), GFP_KERNEL);   
  128. 128   if (!light_devp)   /*分配失败*/   
  129. 129   {   
  130. 130     result =  - ENOMEM;   
  131. 131     goto fail_malloc;   
  132. 132   }   
  133. 133   memset(light_devp, 0, sizeof(struct light_dev));  
  134. 134   light_setup_cdev(light_devp, 0);   
  135. 135   light_init();   
  136. 136   return 0;   
  137. 137      
  138. 138   fail_malloc: unregister_chrdev_region(dev, light_devp);   
  139. 139   return result;   
  140. 140 }   
  141. 141    
  142. 142 /*模块卸载函数*/   
  143. 143 void light_cleanup(void)   
  144. 144 {   
  145. 145   cdev_del(&light_devp->cdev); /*删除字符设备结构体*/   
  146. 146   kfree(light_devp); /*释放在 light_init 中分配的内存*/   
  147. 147   unregister_chrdev_region(MKDEV(light_major, 0), 1); /*删除字符设备*/   
  148. 148 }   
  149. 149    
  150. 150 module_init(light_init);   
  151. 151 module_exit(light_cleanup);  
有操作系统的code相对于没有操作系统的code,包含了大量读者陌生的元素,如结构体file_operations 、 cdev , Linux 内 核 模 块 声 明 用 的 MODULE_AUTHOR 、 MODULE_LICENSE、module_init、module_exit,以及用于字符设备注册、分配和注销用的函数 register_chrdev_region()、alloc_chrdev_region()、unregister_chrdev_region()等。此外设驱动中也增加了 light_init ()、light_cleanup ()、light_read()、light_write()等这样的函数。 

此时,我们只需要有一个感性认识,那就是,上述元素都是 Linux 驱动与内核的接口。Linux 对各类设备的驱动都定义了类似的数据结构和函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值