需要了解嵌入式Linux设备驱动的工作原理

摘要:计算机软件和集成电路技术的发展,为嵌入式产业带来了巨大的机遇和挑战,Linux以其稳定、高效、易定制、硬件广泛支持等特点,迅速崛起为当今计算机领域的一匹黑马。文章通过对与嵌入式Linux设备驱动程序相关内核源码进行分析,从设备驱动的体系结构和内核环境两方面入手,对嵌入式Linux设备驱动程序的工作原理进行剖析和阐述。
关键词: Linux;嵌入式系统;设备驱动;内核环境


0. 引 言
设备驱动程序在Linux内核中占有极其重要的位置,它是内核用于完成对物理设备的控制操作的功能模块。除了CPU、内存以及其他很少的几个部分以外,所有的设备控制操作都必须由与被控设备相关代码——驱动程序来完成。否则设备就无法在Linux下正常工作,这就是驱动程序开发成为Linux内核开发的主要工作的原因。
然而,在嵌入式Linux 系统中,内核提供保护机制,用户空间的进程一般不能直接访问硬件。进行嵌入式系统的开发,很大的工作量是为各种设备编写驱动程序,Linux 设备驱动程序在Linux 内核源代码中占有60%以上,从2.0、2.2 到2.4 版本的内核,源代码的长度日益增加,其实主要是设备驱动程序在增加。
文章通过对与设备驱动相关内核源码进行分析,从设备驱动的内部结构和内核环境两方面入手,对设备驱动程序的工作原理进行深入地剖析和阐述。


1. Linux I/O子系统的体系结构
1.1 I/O子系统的层次结构
Linux的I/O子系统分为上下两个层次:其下层是与设备有关的,即设备驱动程序,它直接控制设备完成具体的I/O操作,并且向上层提供一组访问接口;上层部分是与设备无关的,它根据进程的I/O请求设备驱动程序接口与设备进行通信。由于Linux把设备作为文件管理,所以I/O子系统的上层实际上就是实现文件管理功能的虚拟文件系统VFS,进程的I/O请求经过VFS的转换完成对设备的各种操作,而这些操作的具体实现是由设备驱动来完成的。(如图1所示)
1.2 I/O驱动软件的总体目标
I/O驱动软件的总体目标是将软件组织成一种层次结构,底层软件用来屏蔽具体设备硬件的细节;高层软件则为用户提供一个简洁的界面,从而实现I/O设计的设备无关性。(如图1所示)

需要了解嵌入式Linux设备驱动的工作原理

2. 文件操作例程登记表
2.1 文件系统调用接口

[cpp] view plain copy

 

  • struct file_operations IOdriver_fops =   
  • {  
  •   read  : IOdriver_read,  
  •   write : IOdriver_write,  
  • };  

2.2基本入口点函数结构
我们用以globar_read和read函数为例来对驱动底层软件结构是如何屏蔽设备硬件细节进行剖析。

[cpp] view plain copy

 

  • staTIc ssize_t globar_read(struct file *file, char *buf, size_t len, loff_t *off)  
  • {  
  • if(__copy_to_user(buf,&globar_var, sizeof(int)))  
  • {  
  •     return –EFAULT;  
  • }  
  • return sizeof(int);  
  • }   

上述函数的任务是实现将数据从设备复制到用户空间,也就是将内核空间缓冲区里的数据复制其工作原理如图2所示:
 
需要了解嵌入式Linux设备驱动的工作原理
 
用户程序读操作的入口点到用户空间的缓冲区中,

需要了解嵌入式Linux设备驱动的工作原理


read和global_read函数之间的通信是靠字符设备驱动接口中的file_operaTIons结构,基于Linux-2.4.20的的定义如下:

[cpp] view plain copy

 

  • struct file_operaTIons {  
  • struct module *owner;  
  • /* 指向拥有本结构的module,用于内核维护模块的引用技术 */  
  • loff_t (*llseek) (struct file *, loff_t, int);  
  • /* 改变当前文件的读/写位置并返回新位置,错误返回负数 */  
  • ssize_t (*read) (struct file *, char *, size_t, loff_t);  
  • /* 用来从设备接收数据,返回的非负数表示成功读取的字节数 */  
  • ssize_t (*write) (struct file *,const char *, size_t, loff_t *);  
  • /* 用来往设备发数据,返回的非负数表示成功写入的字节数 */  
  • int(readdir))(struct inode *, struct file *, void *, filldir_t);  
  • /* 仅用于文件系统的目录读取,不用于设备驱动 */  
  • int (*select)(struct inode *, struct file *, int, select table *);  
  • /* 用于查询设备是否可读、可写或处于特殊的状态 */  
  • int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned int );    
  • /* 用于给设备发送命令的接口函数。如驱动不提供,则所有调用失败,返回ENOTTY */   
  • int (*mmap)(struct inode *, struct file *, struct vm_area_struct *);  
  • /* 用于请求将设备内存映射到进程空间。如果驱动不提供,则所有调用失败,返回ENODEV */  
  • int (*open)(struct inode *, struct file *);    
  • /* 打开设备,通常是对设备的第一个操作函数 */  
  • void (*release)(struct inode *, struct file *);   
  • /* 关闭设备。只有当设备文件的所有备份都被释放时,才进行release调用,而不是每次调用close时都执行。 */  
  • int (*fsync)(struct inode *, struct file *);  
  • /* 是系统调用fsync的背后支撑,用户可调用fsync来刷新缓存数据 */  
  • };  

3. Linux驱动工作原理
3.1 驱动程序工作原理图(如图3所示)
 
需要了解嵌入式Linux设备驱动的工作原理
3.2 驱动程序工作原理剖析
3.2.1注册驱动程序
设备驱动是通过insmod命令加载到系统内核中,在内核里由加载模块Init_module()调用注册函数register_chrdev()来完成(如图3所示)。
注册函数格式为:

[cpp] view plain copy

 

  • int register_chrdev(unsigned int major, const char *name, struct file_operaTIon *fops);  

 major :主设备号
 name :设备名
 fops   :驱动程序结构体的地址
 
当注册函数在执行时,首先从字符设备注册表chrdev[ ]的底部(实际是倒数第二个表项)开始向上依次查询各个表项(如图4所示),查到表项的成员项fops为null时,说明它是一个空表项。这时把参数给出的设备名和驱动程序指针集合分别赋予该表项device_struct结构体(也被称为设备描述符,在fs/device.c中)的成员项name和fops,如下所示:
 
需要了解嵌入式Linux设备驱动的工作原理

[cpp] view plain copy

 

  • if (major == 0)  
  • for (major=MAX_CHRDEV-1; major>0; major--)  
  • {if (chrdev[major].fops == NULL)  
  • {chrdev[major].name = name;  
  •  chrdev[major].fops = fops;  
  •  return major;}  
  • return –EBUSY;  
  • }  

3.2.2注销驱动程序
驱动程序的注销是由rmmod命令调用cleanup_module卸载模块,此时注销函数unregister_chrdev()函数就会被调用执行(如图3所示)。当注销函数的参数给出了要注销设备的主设备号major和设备名name后,根据参数major检查注册表项中注册的设备名与参数给出的名字是否一致,若不同则不能注销并返回错误值;如果一致则把该表项的两个成员项都置为null然后返回0值,如下所示:

[cpp] view plain copy

 

  • if (strcmp(chrdev[major].name, name))  
  •    reture –EINVAL;  
  • else  
  •    { chrdev[major].name = NULL;  
  •      chrdev[major].fops = NULL;  
  •   return 0;}  

在Linux中由于设备管理是由内核实现的,所以设备管理的数据结构在内存的内核区,设备管理使用的函数都是内核函数。


4. 结束语
操作系统是通过各种驱动程序来驾驭硬件设备的,它为用户屏蔽了各种各样的设备,驱动硬件是操作系统最基本的功能,并且提供统一的操作方式。设备驱动程序是内核的一部分,硬件驱动程序是操作系统最基本的组成部分,因此熟悉驱动的编写是很重要的。


 

  • 需要了解嵌入式Linux设备驱动的工作原理已关闭评论
    A+
发布日期:2019年07月14日  所属分类:物联网