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不存在这样的问题.
发表评论
-
VC++深入详解??chapter17??笔记
2012-01-20 08:36 523VC++深入详解??chapter17?? ... -
GDB调试.pdf
2012-01-20 08:36 1062GDB调试.pdf 2011年05月26日 1.1 Gd ... -
Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作(续1) - 进程休眠
2012-01-20 08:36 714Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作 ... -
关于Linux下的视频编程
2012-01-20 08:36 562关于Linux下的视频编程 2011年06月20日 前言 ... -
利用反射调用另一个swf里的代码库
2012-01-19 13:42 554利用反射调用另一个swf里的代码库 2011年02月19日 ... -
AIR笔记 使用HTML
2012-01-19 13:42 850AIR笔记 使用HTML 2011年05月17日 a ... -
As3 使用外部swf文件的元件库中的导出类
2012-01-19 13:42 1004As3 使用外部swf文件的元件库中的导出类 2011年08 ... -
优化Flash平台的性能――优化网络交互外部内容
2012-01-19 13:42 812优化Flash平台的性能――优化网络交互外部内容 2010年 ... -
ApplicationDomain
2012-01-19 13:42 517ApplicationDomain 2011年11月01日 ... -
Android智能手机刷机教程
2012-01-17 03:32 1041Android智能手机刷机教程 2011年03月07日 ... -
Linux 一些常用的基本命令
2012-01-17 03:31 523Linux 一些常用的基本命 ... -
地理与我们的衣食住行之出行
2012-01-16 02:07 821地理与我们的衣食住行 ... -
试谈中国地理环境与中国文化的联系
2012-01-16 02:07 705试谈中国地理环境与中国文化的联系 2011年12月20日 ... -
宋代衣食住行等习俗的变化
2012-01-16 02:07 955宋代衣食住行等习俗的变化 2010年03月24日 宋代对 ... -
上海世博呈科技盛宴 互动体验未来的衣食住行
2012-01-16 02:07 521上海世博呈科技盛宴 互 ... -
高中生辉煌60年作文:衣食住行话巨变
2012-01-16 02:07 555高中生辉煌60年作文:衣 ...
相关推荐
需要不断编写新的驱动程序以支持硬件, 通过虚拟字符设备驱动程序的编写,来说明 Linux 系统中字符设备驱动程序的工作 原理。首先介绍了 Linux 系统中设备驱动程序的基本结构, 以及字符设备驱动程序应提供的入口点, ...
本文深入探讨了Linux设备驱动程序的内核机制,并提供了一个简单的字符设备驱动程序示例。通过源码示例,详细讲解了驱动程序注册与注销、文件操作函数的实现、设备号分配等关键概念和操作方法。 通过学习本文,您将...
一篇关于linux字符设备驱动程序原理的文章
Linux设备驱动程序学习(1)-字符设备驱动程序 ·Linux设备驱动程序学习(0)-Hello, world!模块 ·Linux设备驱动程序学习(2)-调试技术 ·Linux设备驱动程序学习(3)-并发和竞态 ·Linux设备驱动程序学习(4)-...
本文主要分析Linux字符设备驱动程序的工作机理。主要内容以及代码片段来源于《LDD3》,俺只是从另外一个角度来讲述。 见过很多关于驱动程序的书,基本上都是告诉你怎么做,然后你STEP BY STEP,然后运行完后结果就...
《LINUX设备驱动程序(第3版)》已针对Linux内核的2610版本彻底更新过了。...对期望了解操作系统内部工作原理的读者来讲,《LINUX设备驱动程序(第3版)》也深入阐述了地址空间、异步事件以及I/O等方面的内容。
Linux下的PCI设备开发以及字符驱动程序框架的研究.pdf
Linux的设备驱动程序开发是嵌入开发中一个有难度的领域,要求开发人员有一定的软硬知识.软件方面要求熟悉Linux内核.硬件方面要求熟悉相关外围设备的功能原理,以及测试工具使用如万用表,示波器. 如果有51单片机的开发...
本书是经典著作《Linux 设备驱动程序》的第三版。该版本已针对 Linux 内核的 2.6.10 彻底更新过了。内核的这个版本针对常见任务完成了合理化设计及相应的简化,比如即插即用,利用sysfs 文件系统和用户空间交互,...
·Linux设备驱动程序学习(1)-字符设备驱动程序 ·Linux设备驱动程序学习(0)-设备驱动介绍& Hello, world!模块 ·Linux设备驱动程序学习(2)-调试技术 ·Linux设备驱动程序学习(3)-并发和竞态 ·Linux设备...
第三章 字符设备驱动程序 scull的设计 主设备号和次设备号 一些重要的数据结构 字符设备的注册 open和release scull的内存使用 read和write 试试新设备 快速参考 ch04.第四章 调试技术 内核中的调试支持 通过打印...
Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很...本文介绍了一个最简单的字符设备驱动程序,提出了设备驱动程序中的一些具体问题。
1.掌握简单字符设备驱动程序编写方法。 2.编写应用程序对驱动程序进行测试,学习应用程序与驱动程序之间的调用过程。 二、实验环境 Linux 3.14.0 嵌入式开发板 三、实验内容及实验原理 1. 实验要求: ①定义一...
第7章 Linux设备驱动程序开发 7.1 设备驱动概述 7.1.1 设备驱动和文件系统的关系 7.1.2 设备类型分类 7.1.3 内核空间和用户空间.. 7.2 设备驱动基础 7.2.1 设备驱动中关键数据结构 7.2.2 字符设备驱动开发 ...
每种驱动程序又各占用一个子目录:如,/block 下为块设备驱动程序,比如ide(ide.c)。如果你希望查看所有可能包含文件系统的设备是如何初始化的,你可以看 drivers/block/genhd.c中的device_se
在本文中,首先概括了网络设备总体特征和工作原理,接着在分析了一个重要的数据...在附录中是一个虚拟的字符设备驱动程序以及写这个程序的体会,该程序已成功使用过,它是在网络设备分析之前本人做的一个小小的试验。
本书是经典著作《Linux 设备驱动程序》的第三版。该版本已针对 Linux 内核的 2.6.10 彻底更新过了。内核的这个版本针对常见任务完成了合理化设计及相应的简化,比如即插即用,利用sysfs 文件系统和用户空间交互,...