Linux设备驱动程序,嵌入式Linux中扩大自身的设备

作者: 韦德国际1946  发布:2019-05-28

驱动程序的行使能够依照二种艺术编写翻译,1种是静态编写翻译进基本,另1种是编译成模块以供动态加载。由于uClinux不帮忙模块动态加载,而且嵌入式Linux不可以象桌面Linux那样灵活的应用insmod/rmmod加载卸载设备驱动程序,因此这里只介绍将配备驱动程序静态编写翻译进uClinux内核的方法。

一、主要知识点

一、Linux device driver 的概念   

       系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机械和工具硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的底细,那样在应用程序看来,硬件装置只是二个装置文件,应用程序可以象操作普通文书1律对硬件道具进行操作。设备驱动程序是水源的一片段,它成功以下的效果:
  一. 对设备初阶化和刑满释放。
  二. 把多少从水源传送到硬件和从硬件读取数据。
  三. 读取应用程序传送给配备文件的数额和回送应用程序请求的数额。
  肆. 检验和拍卖设施出现的一无所长。

  在Linux操作系统下有3类首要的设备文件类型,一是字符设备,2是块设备,三是互连网设施。字符设备和块设备的关键差距是:在对字符设备发生读/写请求时,实际的硬件I/O一般就接着发生了,块设备则不然,它选取壹块系统内部存款和储蓄器作缓冲区,当用户进程对设施请求能满意用户的须求,就回来请求的数据,即便不可能,就调用请求函数来开始展览实际的I/O操作。块设备是根本针对磁盘等慢速设备设计的,以防费用过多的CPU时间来等待。
  已经涉及,用户进度是通过设备文件来与事实上的硬件打交道。各类设备文件都都有其文件属性(c/b),表示是字符设备或许块设备?其它种种文件都有多个道具号,第三个是主设备号,标记驱动程序,第二个是从设备号,标记应用同二个器具驱动程序的不等的硬件设施,比如有多个软盘,就可以用从设备号来分别他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,不然用户进度将不能访问到驱动程序。
  最终必须提到的是,在用户进度调用驱动程序时,系统进入大旨态,这时不再是超越式调整。约等于说,系统必须在你的驱动程序的子函数返回后工夫拓展任何的干活。假使您的驱动程序陷入死循环,不幸的是你唯有重复起动机器了,然后正是经久不衰的fsck。
  读/写时,它首先旁观缓冲区的内容,若是缓冲区的数据 
  怎样编写Linux操作系统下的装置驱动程序

驱动程序的天职

普通来说,驱动(模块)要进行两类职分:

  1. 模块中的有些函数作为系统调用的一片段进行(依照既定规则填补必需的种类调用模块)
  2. 别的函数肩负终端处理

上面以uClinux为例,介绍在三个以模块方式面世的驱动程序test.c基础之上,将其编写翻译进基本的一类别步骤:

 

二、实例深入分析

    我们来写1个最轻巧易行的字符设备驱动程序。就算它怎么着也不做,然而透过它可以通晓Linux的装置驱动程序的办事原理。把上面的C代码输入机器,你就能收获一个的确的器材驱动程序。

[java] view plain copy

 

  1. #define  __NO_VERSION__   
  2. #include  <linux/modules.h>   
  3. #include  <linux/version.h>   
  4.   char kernel_version [] = UTS_RELEASE;   

        那一段定义了一部分版本消息,即使用处不是相当大,但也不可缺少。Johnsonm说有着的驱动程序的始发都要含有<linux/config.h>,一般来说最棒使用。
  由于用户进程是通过设备文件同硬件打交道,对配备文件的操作办法除了就是某些种类调用,如 open,read,write,close…, 注意,不是fopen, fread,但是怎么把系统调用和驱动程序关联起来呢?那必要精通2个那一个主要的数据结构:

[cpp] view plain copy

 

  1. struct file_operations   
  2. {   
  3. int (*seek) (struct inode * ,struct file *, off_t ,int);   
  4. int (*read) (struct inode * ,struct file *, char ,int);   
  5. int (*write) (struct inode * ,struct file *, off_t ,int);   
  6. int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);   
  7. int (*select) (struct inode * ,struct file *, int ,select_table *);   
  8. int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);   
  9. int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);   
  10. int (*open) (struct inode * ,struct file *);   
  11. int (*release) (struct inode * ,struct file *);   
  12. int (*fsync) (struct inode * ,struct file *);   
  13. int (*fasync) (struct inode * ,struct file *,int);   
  14. int (*check_media_change) (struct inode * ,struct file *);   
  15. int (*revalidate) (dev_t dev);   
  16. }   

  那些布局的每七个成员的名字都对应着3个系统调用。用户进度利用种类调用在对配备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的装置驱动程序,然后读取那么些数据结构相应的函数指针,接着把调控权交给该函数。那是linux的器材驱动程序职业的基本原理。既然是如此,则编写设备驱动程序的主要性专门的学业正是编写子函数,并填充file_operations的各种域。
  上面就初叶写子程序。 

[cpp] view plain Linux设备驱动程序,嵌入式Linux中扩大自身的设备驱动程序。copy

 

  1. #include <linux/types.h>   
  2. #include <linux/fs.h>   
  3. #include <linux/mm.h>   
  4. #include <linux/errno.h>   
  5. #include <asm/segment.h>   
  6. unsigned int test_major = 0;   
  7. static int read_test(struct inode *node,struct file *file,char *buf,int count)  
  8. {   
  9. int left;   
  10. if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )   
  11. return -EFAULT;   
  12. for(left = count ; left > 0 ; left--)   
  13. {   
  14. __put_user(1,buf,1);   
  15. buf ;   
  16. }   
  17. return count;   
  18. }   

        这些函数是为read调用希图的。当调用read时,read_test()被调用,它把用户的缓冲区全部写壹。buf 是read调用的三个参数。它是用户进度空间的二个地方。不过在read_test被调用时,系统进入焦点态。所以无法运用buf那个地址,必须用__put_user(),那是kernel提供的贰个函数,用于向用户传送数据。其它还可能有多数类似意义的函数。请参照他事他说加以考察,在向用户空间拷贝数据此前,必须验证buf是不是可用。那就用到函数verify_area。

[cpp] view plain copy

 

  1. static int write_tibet(struct inode *inode,struct file *file,const char *buf,int count)   
  2. {   
  3. return count;   
  4. }   
  5.   
  6. static int open_tibet(struct inode *inode,struct file *file )   
  7. {  
  8. MOD_INC_USE_COUNT;   
  9. return 0;   
  10. }   
  11.   
  12. static void release_tibet(struct inode *inode,struct file *file )   
  13. {   
  14. MOD_DEC_USE_COUNT;   
  15. }   

      那多少个函数都以空操作。实际调用产生时怎么也不做,他们唯有为上面包车型大巴组织提供函数指针。

[cpp] view plain copy

 

  1. struct file_operations test_fops = {   
  2. NULL,   
  3. read_test,   
  4. write_test,   
  5. NULL, /* test_readdir */   
  6. NULL,   
  7. NULL, /* test_ioctl */   
  8. NULL, /* test_mmap */   
  9. open_test,   
  10. release_test,   
  11. NULL, /* test_fsync */   
  12. NULL, /* test_fasync */   
  13. /* nothing more, fill with NULLs */   
  14. };    

     设备驱动程序的重心能够说是写好了。以往要把驱动程序嵌入内核。驱动程序可以遵照三种艺术编译。一种是编写翻译进kernel,另一种是编写翻译成模块(modules),假诺编写翻译进基本的话,会增加基础的分寸,还要更改内核的源文件,而且不能动态的卸载,不便宜调节和测试,所以推举使用模块格局。

[cpp] view plain copy

 

  1. int init_module(void)   
  2. {   
  3. int result;   
  4. result = register_chrdev(0, "test", &test_fops);   
  5. if (result < 0) {   
  6. printk(KERN_INFO "test: can't get major numbern");   
  7. return result;   
  8. }   
  9. if (test_major == 0) test_major = result; /* dynamic */   
  10. return 0;   
  11. }   

       在用insmod命令将编写翻译好的模块调入内部存款和储蓄器时,init_module 函数被调用。在这里,init_module只做了壹件事,便是向系统的字符设备表登记了二个字符设备。register_chrdev须求三个参数,参数一是期望赢得的设施号,假诺是零的话,系统将选用八个未曾被占用的设备号再次来到。参数二是设备文件名,参数叁用来注册驱动程序实际实行操作的函数的指针。
  假若注册成功,再次回到设备的主设备号,不成事,再次回到二个负值。 

[cpp] view plain copy

 

  1. void cleanup_module(void)   
  2. {   
  3. unregister_chrdev(test_major,"test");   
  4. }    

       在用rmmod卸载模块时,cleanup_module函数被调用,它释放字符设备test在系统字符设备表中据为己有的表项。
  一个最为简约的字符设备得以说写好了,文件名就叫test.c吧。 
  下边编写翻译 :

[cpp] view plain copy

 

  1. $ gcc -O2 -DMODULE -D__KERNEL__ -c test.c   

  获得文件test.o正是贰个装置驱动程序。 
  固然设备驱动程序有五个文本,把每种文件按上面包车型客车命令行编写翻译,然后 

[cpp] view plain copy

 

  1. ld  -r  file1.o  file2.o  -o  modulename。   

     驱动程序已经编写翻译好了,现在把它安装到系统中去。 

[cpp] view plain copy

 

  1. $ insmod  –f  test.o   

    假如设置成功,在/proc/devices文件中就能够看出道具test,并得以看到它的主设备号。要卸载的话,运维:

[cpp] view plain copy

 

  1. $ rmmod test   

    下一步要创设设备文件。 

[cpp] view plain copy

 

  1. mknod /dev/test c major minor   

       c 是指字符设备,major是主设备号,正是在/proc/devices里观望的。 
  用shell命令 

[cpp] view plain copy

 

  1. $ cat /proc/devices   

       就足以拿走主设备号,能够把上边包车型大巴命令行插手你的shell script中去。 
  minor是从设备号,设置成0就足以了。 
  我们未来能够透过设备文件来拜会大家的驱动程序。写多个小小的的测试程序。 

[cpp] view plain copy

 

  1. #include <stdio.h>   
  2. #include <sys/types.h>   
  3. #include <sys/stat.h>   
  4. #include <fcntl.h>   
  5. main()   
  6. {   
  7. int testdev;   
  8. int i;   
  9. char buf[10];   
  10. testdev = open("/dev/test",O_RDWR);   
  11. if ( testdev == -1 )   
  12. {   
  13. printf("Cann't open file n");   
  14. exit(0);   
  15. }   
  16. read(testdev,buf,10);   
  17. for (i = 0; i < 10;i )   
  18. printf("%dn",buf[i]);   
  19. close(testdev);   
  20. }   

       编译运维,看看是否打字与印刷出全一 ? 
  以上只是二个粗略的演示。真正实用的驱动程序要复杂的多,要拍卖如中断,DMA,I/O port等问题。那一个才是当真的难关。请看下节,真实景况的拍卖。

       怎么着编写Linux操作系统下的装置驱动程序 

水源中的并发

缘何缅想并发难点:

  1. Linux系统中常见正在运营八个冒出进度,并且只怕有八个经过同期利用咱们的驱动程序
  2. 大繁多配备能够暂停管理器,而打退堂鼓管理程序异步运转,并且恐怕在驱动程序正试图管理别的职分时被调用。
  3. 有一部分软件抽象(例如基本计时器)也在异步运营着
  4. 或然还要有不仅仅二个CPU运转大家的驱动程序

驱动程序编写人员所犯的1个常见错误是,感觉假设某段带代码未有进来睡眠情况(只怕阻塞),就不会时有发生并发难题,但即使在以前的非抢占式内核中,这种假诺也是错误的。在2.陆本子的代码中,内核代码(差不离)始终不可能假定在给定代码段中能够侵占处理器。

(一) 改造test.c源带代码

  1. 程序设备号

3、设备驱动程序中的一些具体难题  

 

 1.  I/O Port

  和硬件打交道离不开I/O Port,老的ISA设备常常是据有实际的I/O端口,在linux下,操作系统未有对I/O口屏蔽,也正是说,任何驱动程序都可对轻便的I/O口操作,那样就很轻便滋生混乱。每一种驱动程序应该团结制止误用端口。
  有多个重要的kernel函数能够有限支撑驱动程序做到那点。 
  1)check_region(int io_port, int off_set) 
  那么些函数察看系统的I/O表,看是或不是有别的驱动程序占用某一段I/O口。 
  参数1:io端口的营地址, 
  参数2:io端口占用的限量。 
  重临值:0 未有据为己有, 非0,已经被占据。 
  2)request_region(int io_port, int off_set,char *devname) 
  如若这段I/O端口未有被攻陷,在大家的驱动程序中就足以应用它。在应用在此以前,必须向系统登记,以免范被其余程序占用。登记后,在/proc/ioports文件中得以观看你登记的io口。
  参数1:io端口的集散地址。 
  参数2:io端口占用的限定。 
  参数三:使用这段io地址的设备名。 
  在对I/O口登记后,就足以放心地用inb(), outb()之类的函来访问了。 
在局地pci设备中,I/O端口被映射到1段内部存款和储蓄器中去,要拜访那么些端口就一定于访问1段内部存款和储蓄器。日常性的,大家要博得一块内部存款和储蓄器的情理地址。

2. 内部存款和储蓄器操作

   在设备驱动程序中动态开荒内存,不是用malloc,而是kmalloc,或许用get_free_pages直接申请页。释放内部存款和储蓄器用的是kfree,或free_pages。 请注意,kmalloc等函数重回的是情理地址!
  注意,kmalloc最大不得不开垦12八k-1陆,十四个字节是被页描述符结构占用了。 
  内部存款和储蓄器映射的I/O口,寄存器可能是硬件装置的RAM(如显存)一般占用F0000000以上的地址空间。在驱动程序中无法直接待上访问,要经过kernel函数vremap获得重新照射现在的地方。
  别的,繁多硬件要求1块一点都非常大的总是内部存款和储蓄器用作DMA传送。那块程序需求直接驻留在内部存款和储蓄器,无法被换来到文件中去。但是kmalloc最三只可以开垦12八k的内存。 
  那足以经过就义局部体系内存的章程来消除。 
  
叁. 脚刹踏板管理    同管理I/O端口一样,要动用八个暂停,必须先向系统注册。

[cpp] view plain copy

 

  1. int request_irq(unsigned int irq ,void(*handle)(int,void *,struct pt_regs *),  
  2. unsigned int long flags, const char *device);   

irq: 是要申请的暂停。 
handle:中断管理函数指针。 
flags:SA_INTEBMWX3RUPT 请求叁个飞跃中断,0 平常中断。 
device:设备名。

  假设注册成功,再次来到0,那时在/proc/interrupts文件中能够看你请求的间歇。 

四.部分周边的标题
  对硬件操作,有的时候时序很重大。不过壹旦用C语言写一些起码的硬件操作的话,gcc往往会对你的先后举行优化,那样时序会发生错误。假设用汇编写呢,gcc同样会对汇编代码进行优化,除非用volatile关键字修饰。最保障的主意是不准优化。那本来只可以对一部分你自个儿编辑的代码。要是对负有的代码都不优化,你会发掘驱动程序根本不恐怕装载。那是因为在编写翻译驱动程序时要用到gcc的一部分扩充本性,而那些扩充性情必须在加了优化增选之后手艺反映出来。

 转自:

一对细节

  • 应用程序具有1块一点都不小的栈空间,内核具备相当的小的栈,它或者唯有40玖陆字节的分寸。借使大家须求大的构造,则应当在调用时动态分配该协会。
  • 基础代码不恩你各省县浮点数运算。若是张开了浮点协助,在有些架构上,须要在进入或退出根本空间时保留和还原浮点管理器的意况。这种额外的支出未有其他价值,内核代码中也无需浮点运算。

第二步,将原来的:

dev_t

  #include 
  #include 
  char kernel_version[]=UTS_RELEASE;
 

         dev_t是水源中用来表示设备编号的数据类型;

 

int MAJOR(dev_t dev)

改动为:

int MINOR(dev_t dev)

  
  #ifdef MODULE
  #include 
  #include 
  char kernel_version[]=UTS_RELEASE;
  #else
  #define MOD_INC_USE_COUNT
  #define MOD_DEC_USE_COUNT
  #endif

         那七个宏抽出主次设备号。

其次步,新建函数int init_test(void)

dev­_t MKDEV(unsigned int major, unsignedint minor)

将设备注册写在这里:

         那几个宏由主/次设备号构造1个dev_t结构。

  result=register_chrdev(254,"test",&test_fops);

 

  

  1. 分红和释放设备号

(2) 将test.c复制到/uclinux/linux/drivers/char目录下,并且在/uclinux/linux/drivers/char目录下mem.c中,int chr_dev_init( )函数中追加如下代码:

int register_chardev_region(dev_t first,unsigned int count, char *name)

  #ifdef CONFIG_TESTDRIVE
  init_test();
  #endif

         静态申请设备号。

  

Int alloc_chardev_region(dev_t *dev,unsigned int firstminor, unsigned int count, char *name)

(三) 在/uclinux/linux/drivers/char目录下Makefile中扩张如下代码:

         动态申请设备号,注意第二个参数是传地址,而静态则是传值。

  ifeq($(CONFIG_TESTDRIVE),y)
  L_OBJS =test.o
  Endif

        

  

         叁. 二种关键的数据结构

(四) 在/uclinux/linux/arch/m6八knommu目录下config.in中字符设备段里扩大如下代码:

struct file

  bool 'support for testdrive' CONFIG_TESTDRIVE y

         file结构意味着2个张开的文书,它由基础在open时创设,并传递给该文件上开始展览操作的持有函数,直到最后的close函数。

本文由韦德国际1946发布于韦德国际1946,转载请注明出处:Linux设备驱动程序,嵌入式Linux中扩大自身的设备

关键词: Linux linux驱动