`
vu060vu
  • 浏览: 12636 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

LINUX字符设备驱动程序原理总结

 
阅读更多

LINUX字符设备驱动程序原理总结
2010年07月08日
  一)设备的输入/输出原理
  通常,任何数据都必须通过内核空间才能到达应用程序的缓冲上。例如:对一个设备的读操作会引起数据被至少复制两遍,一遍是将内容复制到内核缓冲中,另一遍是将其再次复制到用户缓冲中。这是为了保证数据的可靠性和安全性所付出的代价。
  但是,当字符设备驱动程序在低速字符设备上读写操作时,它通常直接将数据从用户空间的缓冲中复制到设备上。
  二)I/O和字符设备
  字符设备包括两种类型的设备: 1)低速的字符设备,也就是流设备,一般是终端和连续的端口。 在流设备上不能进行随机访问,也就是只能调用read和write来实现与该设备的通信。而不能使用mmap。由于read和write是同步的,当进程进行write和read调用时,它通常会阻塞,并要一直到操作完成后才能从系统调用里返回。这意味着在存储完成后驱动程序才会允许进程继续执行.本来可以用于运行代码的时间浪费在等待设备驱动程序上.
  用下面的例子说明这一点:
  time dd if=/dev/zero of=/dev/tty bs=1M count=10
  10+0 records in
  10+0 records out
  10485760 bytes (10 MB) copied, 1.99129 s, 5.3 MB/s
  real    0m1.997s
  user    0m0.000s
  sys     0m0.144s
  这里只向终端写了堆空字符串,整个命令用了1.997秒,而实际上花在CPU的时间却只有0.144s,结论是剩下的时间都用在了驱动程序的阻塞上。 2)高速的字符设备 在高速的字符设备上允许随机访问,也就是支持mmap系统调用,这使得应用程序可以查看该设备驱动程序必须提供的所有数据.一般是很大的一个存储区域.
  一个支持mmap系统调用的字符设备驱动程序,是不需要read和write调用的.因为它的读写操作会变得几乎像配给一大块内存一样简单.从而可以减少对系统调用的使用.
  mmap()函数用来将某个文件内容映射到内存中,对该内存区域的访问即是直接对该文件内容读写.调用成功返回映射区的内存起始地址,进程可直接操作起始地址为该值的有效地址,否则返回-1。
  一个简单的mmap示例:
  #include 
  #include 
  #include 
  #include 
  #include 
  #define ERROR(x) do{ perror(x);\
  exit (EXIT_FAILURE); } while(0)
  int
  main (int argc, char *argv[])
  {
  const int nbytes = 4096;
  void *ptr;
  int fd = open("/dev/zero", O_RDWR);  //打开/dev/zero设备
  if (fd == -1) ERROR("open");
  ptr = mmap(0, nbytes, PROT_READ|PROT_WRITE,
  MAP_PRIVATE, fd, 0); //调用mmap函数,将/dev/zero设备节点映射到*ptr中的地址,
  //这个内存区域大小为4096个字节,权限是可读/可写
  if (ptr == MAP_FAILED) ERROR("mmap");
  memset(ptr, 1, nbytes);        //填充*ptr这个内存区域为1
  munmap(ptr, nbytes);         //释放*ptr这个内存映像
  return 0;
  }
  三)块设备,文件系统和I/O
  1)块设备是磁盘及其他使用文件系统的存储设备的基础.
  2)要访问一个设备,可以有两种方法:直接访问或者通过文件系统访问.
  直接访问设备:
  .分区:fdisk
  .格式化:mkfs
  .对设备节点的复制:cp /dev/sda sda.img或者 dd /dev/sda f=sda.img bs=8M
  通过文件系统访问:
  .通过mount命令挂载:mount /dev/sda1 /mnt/disk1之后对/mnt/disk1进行访问
  用一个例子来说明两种访问方式的区别
  备份整个磁盘/dev/sda的内容
  直接访问方式:
  cp /dev/sda sda.img
  通过文件系统访问方式:
  mount /dev/sda1 /mnt/disk1
  tar cvzf /mnt/disk1 sda.tar.gz
  总结两种方式的区别:
  1)直接通过设备节点备份,可以备份磁盘的所有内容,包括启动块,而通过文件系统方式进行备份则不包括启动块等信息。
  2)直接通过设备节点备份是备份了磁盘的所有数据,即磁盘/分区为30GB,备份后的文件也是30GB,而通过对文件系统打包备份,则有多少数据则打包多少数据。
  四)缓冲区缓存和文件系统缓存
  1)缓冲区缓存用来存储块设备可写和可读的块,在操作系统中叫buffer,由块设备驱动程序进行管理.它有以下几个特点:
  .当一个进程要向一个块设备写数据时,首先数据会被复制到缓冲区缓存中的一个块里.块设备驱动程序不是马上被调用,当内核决定应该将数据块写回设备上时,它才会调用驱动程序.
  .内核原则上会尽可能久地将每个读写块的副本保存在缓冲区缓存中..优点一是优化了系统的性能,因为想从磁盘上读数据的进程,只要从高速存储器中读取就可以了..优点二是cache允许内核把临近的数据块连接起来,将它们合并成一个大的写磁盘,这样可以提高磁盘的利用率..优点三cache的应用减少了启动磁盘的次数,因为在被写回磁盘前,一个块已经更新过了,这样内核只需要执行一次写操作而不需要两次.
  .缺点是如果系统崩溃或者掉电,而数据如果还没写到设备上时,数据就会丢失.
  应用缓冲区缓存的例子:
  第一步查看当前系统中memory的buffers的大少,这里是40400
  [root@test1 ~]# free
  total       used       free     shared    buffers     cached
  Mem:        515600     221304     294296          0      40400     143780
  -/+ buffers/cache:      37124     478476
  Swap:      1052248          0    1052248
  第二步从/dev/zero上的一个字符设备复制到了4MB数据到ramdisk设备/dev/ram0上的一个块设备.
  [root@test1 ~]# dd if=/dev/zero f=/dev/ram0 bs=1k count=4096
  第三步查看当前系统中memory的buffers的大小,这里是44428,正好是4MB
  [root@test1 ~]# free
  total       used       free     shared    buffers     cached
  Mem:        515600     225148     290452          0      44428     143780
  -/+ buffers/cache:      36940     478660
  Swap:      1052248          0    1052248
  说明:
  因为上例是与块设备(ramdisk)的互动,所以他就用到了缓冲区缓存.ramdisk设备的一个特性是一旦它分配内存,这块内存空间就不再释放,但不是所有的块设备都会这样做.
  2)文件系统缓存的数据是由文件系统驱动程序管理,在操作系统中叫cache.
  它有以下几个特点:
  .在数据被写到磁盘之前,它会先被复制到文件系统的高速缓存中.
  .内核从磁盘读数据之前,也会试图从文件系统高速缓存中读,并且每次从设备中读到的数据都会复制到文件系统高速缓存中.
  应用操作系统缓存的例子:
  第一步先在ram0设备节点上创建文件系统
  [root@test1 ~]# mkfs -t ext3 /dev/ram0
  mke2fs 1.39 (29-May-2006)
  Filesystem label=
  OS type: Linux
  Block size=4096 (log=2)
  Fragment size=4096 (log=2)
  4096 inodes, 4096 blocks
  204 blocks (4.98%) reserved for the super user
  First data block=0
  1 block group
  32768 blocks per group, 32768 fragments per group
  4096 inodes per group
  Writing inode tables: done                            
  Creating journal (1024 blocks): done
  Writing superblocks and filesystem accounting information: done
  This filesystem will be automatically checked every 25 mounts or
  180 days, whichever comes first.  Use tune2fs -c or -i to override.
  第二步查看cached的大小,为143864
  [root@test1 ~]# free
  total       used       free     shared    buffers     cached
  Mem:        515600     225952     289648          0      45048     143864
  -/+ buffers/cache:      37040     478560
  Swap:      1052248          0    1052248
  第三步挂载/dev/ram0到/mnt
  [root@test1 ~]# mount /dev/ram0 /mnt/
  第四步查看cached的大小,这里没有变化
  [root@test1 ~]# free
  total       used       free     shared    buffers     cached
  Mem:        515600     226108     289492          0      45048     143864
  -/+ buffers/cache:      37196     478404
  Swap:      1052248          0    1052248
  第五步在文件系统/mnt中创建一个2MB的文件.
  [root@test1 ~]# dd if=/dev/zero f=/mnt/zero.dat bs=1k count=2048
  2048+0 records in
  2048+0 records out
  2097152 bytes (2.1 MB) copied, 0.0154719 seconds, 136 MB/s
  第六步最后再查看cached的大小,这里为145912
  [root@test1 ~]# free
  total       used       free     shared    buffers     cached
  Mem:        515600     228092     287508          0      45052     145912
  -/+ buffers/cache:      37128     478472
  Swap:      1052248          0    1052248
  说明:
  新建的文件将一直保存在cache中,直到如下的事件之一发生:
  .新的数据需要占有cache.
  .文件被删除.
  .文件系统没有挂载上.
  .由于进程,内核要刷新空闲内存空间.
  .一个应用程序通过调用sync或者fdatasync直接刷新数据.
  关于文件被删除释放cache中的例子如下:
  第一步,查看cached的大小,这里为145912
  [root@test1 ~]# free
  total       used       free     shared    buffers     cached
  Mem:        515600     228092     287508          0      45052     145912
  -/+ buffers/cache:      37128     478472
  Swap:      1052248          0    1052248
  第二步,删除文件系统中的文件/mnt/zero.dat
  [root@test1 ~]# rm /mnt/zero.dat 
  rm: remove regular file `/mnt/zero.dat'? y
  第三步,查看cached的大小,这里为143908,说明删除文件zero.dat,确实是释放了cache.
  [root@test1 ~]# free
  total       used       free     shared    buffers     cached
  Mem:        515600     228648     286952          0      47108     143908
  -/+ buffers/cache:      37632     477968
  Swap:      1052248          0    1052248
  关于卸载文件系统释放cache的例子如下:
  第一步,在文件系统/mnt中新建4MB的文件
  [root@test1 ~]# dd if=/dev/zero f=/mnt/zero1.dat bs=1k count=4096
  4096+0 records in
  4096+0 records out
  4194304 bytes (4.2 MB) copied, 0.0343595 seconds, 122 MB/s
  第二步,查看当前的cached,这里是147988.
  [root@test1 ~]# free
  total       used       free     shared    buffers     cached
  Mem:        515600     232244     283356          0      47108     147988
  -/+ buffers/cache:      37148     478452
  Swap:      1052248          0    1052248
  第三步,卸载挂载的目录/mnt/
  [root@test1 ~]# umount /mnt/
  第四步,查看当前的cached,这里是143908,说明卸载文件系统,确实是释放了cache.
  [root@test1 ~]# free
  total       used       free     shared    buffers     cached
  Mem:        515600     230632     284968          0      49156     143908
  -/+ buffers/cache:      37568     478032
  Swap:      1052248          0    1052248
  3)缓冲区缓存或文件系统缓存与进程之间关于内存的竞争
  3.1)缓冲区缓存或文件系统cache是空闲的内存空间,因为他们能被刷新为更多的进程腾出空间.
  3.2)一个忙于I/O操作的系统会占用大量的系统内存空间,如缓冲区缓存或者文件系统的cache.
  3.3)如果进程对内存的需要大于当前系统可用的,系统就必须想办法释放空间.为释放空间,内核通过刷新块来收回高速缓冲存储器.
  4)内核收回高速缓冲存储器
  4.1)内核从cache中获得空间将最老的块写回磁盘上.
  4.2)干净的块(clean block)指无需磁盘I/O便可以立刻收回的块.干净的块可以是一个已被磁盘读过但还未被修改的块,也可以是一个被写回磁盘但未被回收的块.
  4.3)脏块(dirty block)指那些已经修改过的块或被创建,且其修改或创建还没有写回磁盘的块.
  五)内核管理文件系统缓存的原理
  内核一般通过后台进程pdflush定期将数据写回到磁盘,它负责确保不让数据过长地占用文件系统缓存(cache).
  例如:在一个空闲的系统中,写了1MB的数据到文件,这个操作会引起内存中出现1MB的dirty cache块.为了防止数据无限期驻留在内存,所以通过pdflush定期限将dirty cache块写回到设备上.在大多数Linux的发行版中这个时间默认为30秒.
  我们再也说下pdflush:
  1)严格说来,pdflush不是一个进程,而是一个内核线程.
  2)一个进程式存在于两个空间.内核线程是一个没有用户空间,完全在内核空间中运行的进程.它是通过由内核定义的函数直接开启,并没有可执行文件.
  3)可以有两种方法识别内核线程:
  方法1)
  查看/proc/PID/maps,在一个普通进程里,它会显示其虚拟内存映射.而在内核线程没有用户空间,它的映射文件总会显示空.
  例如:
  查看init进程,它是一个普通进程.
  [root@test1 ~]# more /proc/1/maps 
  003c2000-003fd000 r-xp 00000000 08:01 3704519    /lib/libsepol.so.1
  003fd000-003fe000 rwxp 0003a000 08:01 3704519    /lib/libsepol.so.1
  003fe000-00408000 rwxp 003fe000 00:00 0 
  004aa000-004bf000 r-xp 00000000 08:01 3704520    /lib/libselinux.so.1
  004bf000-004c1000 rwxp 00015000 08:01 3704520    /lib/libselinux.so.1
  00b51000-00b6a000 r-xp 00000000 08:01 3704501    /lib/ld-2.5.so
  00b6a000-00b6b000 r-xp 00018000 08:01 3704501    /lib/ld-2.5.so
  00b6b000-00b6c000 rwxp 00019000 08:01 3704501    /lib/ld-2.5.so
  00b6e000-00ca5000 r-xp 00000000 08:01 3704502    /lib/libc-2.5.so
  00ca5000-00ca7000 r-xp 00137000 08:01 3704502    /lib/libc-2.5.so
  00ca7000-00ca8000 rwxp 00139000 08:01 3704502    /lib/libc-2.5.so
  00ca8000-00cab000 rwxp 00ca8000 00:00 0 
  00cd6000-00cd8000 r-xp 00000000 08:01 3704503    /lib/libdl-2.5.so
  00cd8000-00cd9000 r-xp 00001000 08:01 3704503    /lib/libdl-2.5.so
  00cd9000-00cda000 rwxp 00002000 08:01 3704503    /lib/libdl-2.5.so
  00d6d000-00d6e000 r-xp 00d6d000 00:00 0          [vdso]
  08048000-08050000 r-xp 00000000 08:01 4915359    /sbin/init
  08050000-08051000 rw-p 00008000 08:01 4915359    /sbin/init
  09fe6000-0a007000 rw-p 09fe6000 00:00 0 
  b7fb5000-b7fb7000 rw-p b7fb5000 00:00 0 
  b7fcb000-b7fcc000 rw-p b7fcb000 00:00 0 
  bf82a000-bf83f000 rw-p bf82a000 00:00 0          [stack]
  结果显示出它所有的映射文件.
  查看pdflush内核线程,首先得到它的PID为136
  ps -ef|grep pdflush
  root       136     7  0 11:34 ?        00:00:00 [pdflush]
  root       137     7  0 11:34 ?        00:00:00 [pdflush]
  查看进程ID为136的maps
  [root@test1 ~]# more /proc/136/maps 
  [root@test1 ~]# 
  显示为空,说明它是一个内核线程
  方法2)
  查看pdflush的父进程
  [root@test1 ~]# ps -ajxf
  PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
  1     7     1     1 ?           -1 S<       0   0:00 [kthread]
  7    10     1     1 ?           -1 S<       0   0:00  \_ [kblockd/0]
  7    11     1     1 ?           -1 S<       0   0:00  \_ [kacpid]
  7    68     1     1 ?           -1 S<       0   0:00  \_ [cqueue/0]
  7    71     1     1 ?           -1 S<       0   0:00  \_ [khubd]
  7    73     1     1 ?           -1 S<       0   0:00  \_ [kseriod]
  7   136     1     1 ?           -1 S        0   0:00  \_ [pdflush]
  7   137     1     1 ?           -1 S        0   0:00  \_ [pdflush]
  7   138     1     1 ?           -1 S<       0   0:00  \_ [kswapd0]
  7   139     1     1 ?           -1 S<       0   0:00  \_ [aio/0]
  7   290     1     1 ?           -1 S<       0   0:00  \_ [kpsmoused]
  7   322     1     1 ?           -1 S<       0   0:00  \_ [scsi_eh_0]
  7   323     1     1 ?           -1 S<       0   0:00  \_ [kjournald]
  7   351     1     1 ?           -1 S<       0   0:00  \_ [kauditd]
  7  1004     1     1 ?           -1 S<       0   0:00  \_ [kmirrord]
  pdflush的父进程是kthread,说明它是由内核线程派生出的一个内核线程.
  最后需要说明的是,在以下两种情况时,内核不会用pdflush将文件系统缓存的数据写回磁盘.
  情况1)因为dirty blocks最有可能是最近使用过的,所以内核不太可能会刷新它们,但当系统正忙于I/O操作时,就有可能刷新它们,在这种情况下,不需要pdflush做刷新工作,而由当前运行的进程来将dirty block写入到磁盘中.
  情况2)应用程序通过fsync,fdataync和sync强迫将特殊文件块提前写回磁盘.
  我们最后需要知道最先收回的块是最老的块,或者是最近最少使用的块.在没有进程进行刷新dirty block和强制的sync时,最后会由pdflush将dirty block写回磁盘.
  为什么都是dirty block写回磁盘呢,因为改过的块不写入磁盘,会因为掉电,当机等情况丢失数据,而clean block不存在这样的问题.
分享到:
评论

相关推荐

    虚拟字符设备驱动程序在Linux的实现

    需要不断编写新的驱动程序以支持硬件, 通过虚拟字符设备驱动程序的编写,来说明 Linux 系统中字符设备驱动程序的工作 原理。首先介绍了 Linux 系统中设备驱动程序的基本结构, 以及字符设备驱动程序应提供的入口点, ...

    深入Linux设备驱动程序内核机制.pdf

    本文深入探讨了Linux设备驱动程序的内核机制,并提供了一个简单的字符设备驱动程序示例。通过源码示例,详细讲解了驱动程序注册与注销、文件操作函数的实现、设备号分配等关键概念和操作方法。 通过学习本文,您将...

    字符设备驱动原理

    一篇关于linux字符设备驱动程序原理的文章

    Linux设备驱动程序学习

    Linux设备驱动程序学习(1)-字符设备驱动程序 ·Linux设备驱动程序学习(0)-Hello, world!模块 ·Linux设备驱动程序学习(2)-调试技术 ·Linux设备驱动程序学习(3)-并发和竞态 ·Linux设备驱动程序学习(4)-...

    Linux字符设备驱动程序工作机理分析

    本文主要分析Linux字符设备驱动程序的工作机理。主要内容以及代码片段来源于《LDD3》,俺只是从另外一个角度来讲述。 见过很多关于驱动程序的书,基本上都是告诉你怎么做,然后你STEP BY STEP,然后运行完后结果就...

    LINUX设备驱动程序

    《LINUX设备驱动程序(第3版)》已针对Linux内核的2610版本彻底更新过了。...对期望了解操作系统内部工作原理的读者来讲,《LINUX设备驱动程序(第3版)》也深入阐述了地址空间、异步事件以及I/O等方面的内容。

    Linux下的PCI设备开发以及字符驱动程序框架的研究.pdf

    Linux下的PCI设备开发以及字符驱动程序框架的研究.pdf

    Linux设备驱动程序开发基础

    Linux的设备驱动程序开发是嵌入开发中一个有难度的领域,要求开发人员有一定的软硬知识.软件方面要求熟悉Linux内核.硬件方面要求熟悉相关外围设备的功能原理,以及测试工具使用如万用表,示波器. 如果有51单片机的开发...

    Linux设备驱动程序第三版2.6.CHM

    本书是经典著作《Linux 设备驱动程序》的第三版。该版本已针对 Linux 内核的 2.6.10 彻底更新过了。内核的这个版本针对常见任务完成了合理化设计及相应的简化,比如即插即用,利用sysfs 文件系统和用户空间交互,...

    Linux 驱动学习笔记pdf文档

    ·Linux设备驱动程序学习(1)-字符设备驱动程序 ·Linux设备驱动程序学习(0)-设备驱动介绍& Hello, world!模块 ·Linux设备驱动程序学习(2)-调试技术 ·Linux设备驱动程序学习(3)-并发和竞态 ·Linux设备...

    linux设备驱动程序

    第三章 字符设备驱动程序 scull的设计 主设备号和次设备号 一些重要的数据结构 字符设备的注册 open和release scull的内存使用 read和write 试试新设备 快速参考 ch04.第四章 调试技术 内核中的调试支持 通过打印...

    编写Linux系统的设备驱动程序

    Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很...本文介绍了一个最简单的字符设备驱动程序,提出了设备驱动程序中的一些具体问题。

    XDU嵌入式驱动程序设计 实验一 简单字符设备驱动

    1.掌握简单字符设备驱动程序编写方法。 2.编写应用程序对驱动程序进行测试,学习应用程序与驱动程序之间的调用过程。 二、实验环境 Linux 3.14.0 嵌入式开发板 三、实验内容及实验原理 1. 实验要求: ①定义一...

    嵌入式设计及linux驱动开发指南——基于ARM9处理器.pdf

    第7章 Linux设备驱动程序开发 7.1 设备驱动概述 7.1.1 设备驱动和文件系统的关系 7.1.2 设备类型分类 7.1.3 内核空间和用户空间.. 7.2 设备驱动基础 7.2.1 设备驱动中关键数据结构 7.2.2 字符设备驱动开发 ...

    嵌入式Linux驱动开发基础总结

    每种驱动程序又各占用一个子目录:如,/block 下为块设备驱动程序,比如ide(ide.c)。如果你希望查看所有可能包含文件系统的设备是如何初始化的,你可以看 drivers/block/genhd.c中的device_se

    Linux网络设备分析

    在本文中,首先概括了网络设备总体特征和工作原理,接着在分析了一个重要的数据...在附录中是一个虚拟的字符设备驱动程序以及写这个程序的体会,该程序已成功使用过,它是在网络设备分析之前本人做的一个小小的试验。

    Linux设备驱动第三版

    本书是经典著作《Linux 设备驱动程序》的第三版。该版本已针对 Linux 内核的 2.6.10 彻底更新过了。内核的这个版本针对常见任务完成了合理化设计及相应的简化,比如即插即用,利用sysfs 文件系统和用户空间交互,...

Global site tag (gtag.js) - Google Analytics