Chinaunix首页 | 论坛 | 博客
  • 博客访问: 795717
  • 博文数量: 142
  • 博客积分: 3505
  • 博客等级: 中校
  • 技术积分: 1501
  • 用 户 组: 普通用户
  • 注册时间: 2011-07-30 19:30
文章分类

全部博文(142)

文章存档

2012年(33)

2011年(109)

分类: LINUX

2011-10-04 11:25:11

)设备的输入/输出原理
通常,任何数据都必须通过内核空间才能到达应用程序的缓冲上。例如:对一个设备的读操作会引起数据被至少复制两遍,一遍是将内容复制到内核缓冲中,另一遍是将其再次复制到用户缓冲中。这是为了保证数据的可靠性和安全性所付出的代价。

但是,当字符设备驱动程序在低速字符设备上读写操作时,它通常直接将数据从用户空间的缓冲中复制到设备上。

 

)I/O和字符设备

字符设备包括两种类型的设备:

1)低速的字符设备,也就是流设备,一般是终端和连续的端口。

在流设备上不能进行随机访问,也就是只能调用readwrite来实现与该设备的通信。而不能使用mmap。由于readwrite是同步的,当进程进行writeread调用时,它通常会阻塞,并要一直到操作完成后才能从系统调用里返回。这意味着在存储完成后驱动程序才会允许进程继续执行.本来可以用于运行代码的时间浪费在等待设备驱动程序上.


用下面的例子说明这一点:
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系统调用的字符设备驱动程序,是不需要readwrite调用的.因为它的读写操作会变得几乎像配给一大块内存一样简单.从而可以减少对系统调用的使用.

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的应用减少了启动磁盘的次数,因为在被写回磁盘前,一个块已经更新过了,这样内核只需要执行一次写操作而不需要两次.

.缺点是如果系统崩溃或者掉电,而数据如果还没写到设备上时,数据就会丢失.

 

应用缓冲区缓存的例子:

第一步查看当前系统中memorybuffers的大少,这里是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

第三步查看当前系统中memorybuffers的大小,这里是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的数据到文件,这个操作会引起内存中出现1MBdirty 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]

查看进程ID136maps
[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,fdatayncsync强迫将特殊文件块提前写回磁盘.

 

我们最后需要知道最先收回的块是最老的块,或者是最近最少使用的块.在没有进程进行刷新dirty block和强制的sync,最后会由pdflushdirty block写回磁盘.

为什么都是dirty block写回磁盘呢,因为改过的块不写入磁盘,会因为掉电,当机等情况丢失数据,clean block不存在这样的问题.

阅读(2835) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~