本人再看深刻理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,以为Linux系统编程仍是简单易懂些,而且两本书都是讲Linux比较底层的东西,只不过侧重点不一样,本文就以Linux系统编程为例而且会穿插一些深刻理解Linux内核的内容来写。html
Linux内核3.9,gcc编译器4.8,C库2.17node
文件必须打开才能访问linux
同一个文件能够由多个进程或者同一个进程屡次打开。系统会为每一个打开的文件实例提供惟一描述符。进程能够共享文件描述符,用户空间的程序通常须要本身协调来确保对文件的同步访问是合理的。ios
文件一般是经过文件名访问的,经过索引节点很麻烦,目录上存放是索引节点和文件名的对应关系。文件名和inode是经过hlist_head,hlist_node这两个数据结构来实现哈希列表的访问方式。算法
Linux内核采用缓存(dentry cache)存储目录的解析结果,使用相对路径的时候,内核会获取当前的目录的绝对目录,用相对路径和当前工做目录的组合获得绝对路径。shell
当不一样的名字的多个连接映射到一个索引接点上时,这个连接为硬连接。
索引节点删除须要link count为0.编程
有inode和block,block中放的是连接的文件的绝对路径数组
Linux只支持四种特殊文件:块设备文件,字符设备文件,命名管道,UNIX域套接字。特殊文件遵循一切皆文件的理念。
字符设备是做为线性队列来访问的。漏读数据或者不按顺序读是不可能的。
块设备是做为字节数组来访问。能够随机访问数组中的任何字节。缓存
块设备的最小寻址单元是扇区sector。通常是512b
文件系统的最小单位是块block,通常sector<block<page(内存的最小寻址单元)
UNIX系统只有一个共享的命名空间,对全部的用户和进程均可见。Linux支持进程间的独立的命名空间,容许每一个进程均可以持有系统文件和目录层次的惟一视图。默认状况下,每一个进程都继承父进程的命名空间,可是进程也能够选择建立本身的命名空间,包含本身的挂载点和独立的根目录。安全
进程是执行时的目标代码:活动的,正在运行的程序。可是进程不只包含目标代码,它还包含数据,资源,状态和虚拟计算机。
最重要的是文本段,数据段,bss段。文本段包含可执行代码和只读数据如常量,一般标记为只读和可执行。数据段包含初始化的数据,包含给定C变量,一般标记为可读写。bss(block started by symbol)段包含未初始化的全局数据。bss段的设计彻底是出于性能优化。
线程包含栈,处理器状态,目标代码的当前位置。进程的其余部分由全部线程共享,最主要的是进程地址空间。
线程和轻量级进程都是操做系统相关的概念,Linux内核实现了独特的线程模型,它们实际上是共享某些资源的不一样进程。
Linux还支持访问控制列表。ACL支持更详细的权限和安全控制方式,代价是复杂度变大和磁盘开销。
信号是一种单向异步的通知机制,因为信号的异步性,处理函数须要注意不要破坏以前的代码,只执行异步安全(async-safe,信号安全)的函数。
在对文件进行读写操做以前,首先要打开文件,内核会为每一个进程维护一个打开文件的列表,这个列表是由一些非负整数进行索引,这些非负整数称为文件描述符。
文件描述符从0开始到上限值-1,每一个进程至少包含3个文件描述符:0,1,2,除非显示的关闭这些描述符。
遵循一切接文件的理念,几乎全部可以读写的东西均可以经过文件描述符来访问。
umask是进程级的属性,由login shell设置经过umask来修改。umask为022,取反为755,而后与mode参数0666取与为0644,新建文件的权限为644。
用户空间发起write()系统调用时,Linux内核会作几个检查,而后把数据从提供的缓冲区拷贝到内核缓冲区。而后后台内核收集全部的脏页,进行排序优化,而后再写入磁盘(这个过程称为回写)。这就是延迟写。为了保证数据按时写入,内核设置了最大缓存时效(maximum buffer age),经过/proc/sys/vm/dirtytime_expire_seconds ,单位是厘秒(0.01秒)。linux也支持强制文件缓存写回,甚至是将全部的写操做同步。
下面是我Ubuntu虚拟机的设置
root@wis-virtual-machine:~# cat /proc/sys/vm/dirtytime_expire_seconds 43200
Linux内核提供了一些选择能够牺牲性能换来同步操做。
fsync是在硬件驱动器确认数据和元数据都写到磁盘以后返回,对于包含写缓存的磁盘,fsync没法知道数据是否已经真正在屋里磁盘上了,硬盘会报告说数据已经写完了,实际上数据还在磁盘的写缓存上。
fdatasync会写文件的大小,fdatasync不保证非基础的元数据也写到磁盘上,它比fsync更快,它不考虑元数据如文件修改时间戳。
这两个函数不保证文件相关的目录也写到磁盘上,因此也须要对更新的目录使用同步函数。
O_SYNC能够理解为调用write以后再调用fsync。
O_DSYNC能够理解为调用write以后再调用fdatasync。
O_DIRECT位使I/O操做都会忽略页缓存机制,直接对用户空间缓冲区和设备进行初始化。全部的I/O都是同步的。使用直接I/O时,请求长度,缓冲区以及文件偏移都必须是底层设备扇区大小(通常是512字节)的整数倍。
时间局部性原理是认为刚被访问的资源在不久以后再次被访问的几率很高。
空间局部性原理是认为数据访问每每是连续的。预读功能就是利用的这个原理。
一下两种状况会发生页回写:
要读取1024个字节,若是每次只读取一个字节须要执行1024次系统调用,若是一次读取1024个字节那么就只须要一次系统调用。对于前一种提高性能的方法是用户缓冲I/O(user-buffered I/O),读写数据并无任何变化,而实际上,只有数据量大小达到文件系统块大小张数倍的时候,才会执行真正的I/O操做。
块大小通常是512,1024,2048,4096个字节,I/O操做最简单的方式是设置一个较大的缓冲区,是标准块的整数倍,好比设置成4096,或者8192。
实际应用程序的读写都是在本身的缓冲区。写的时候数据会被存储到程序地址空间的缓冲区,当缓冲区数据大小达到给定值的时候,整个缓冲区会经过一次写操做所有写出。读也是一次读入用户缓冲区大小且块对齐的数据,应用读取数据是从缓冲区一块一块读的,当缓冲区为空时,会读取另外一个块对齐的数据,。经过这种方式,虽然设置的读写大小很不合理,数据会从缓冲区中读取,所以对文件系统仍是发送大的块对齐的读写请求。其结果是对于大量数据,系统调用次数更少,且每次读请求的数据大小都是块对齐的。经过这种方式,能够确保有很大的性能提高。
C标准库中提供了标准I/O库,它实现了跨平台的用户缓冲解决方案。
应用是使用标准I/O(能够定制用户缓冲行为),仍是直接使用系统调用,这些都是开发人员应该慎重权衡应用的需求和行为后肯定。
标准I/O程序集不是直接操做文件描述符,他们经过惟一表示符,即文件指针来操做。在C标准库里,文件指针和文件描述符一一映射。文件指针是由指向FILE的指针表示,FILE定义在<stdio.h>中。
在标准I/O中,打开的文件成为流(stream)。流能够用来读(输入流),写(输出流)或者并且都有(输入/输出流)。
当不存在和流相关的标准I/O函数时,能够经过文件描述符对该流执行系统调用。为了得到和流相关的文件描述符,可使用fileno函数:
# include <stdio.h> int fileno(FILE *stream);
成功时返回关联的文件描述符。最好永远都不要混合使用文件描述符和基于流的I/O操做。
标准I/O提供了三种类型的用户缓冲:
在访问共享数据时,有两种方式能够避免修改它:
标准I/O函数本质上是线程安全的,每一个函数内部都关联了一把锁,一个锁计数器,以及持有该锁并打开一个流的线程。每一个线程在执行任何I/O请求以前,都必须持有该锁。所以,在单个函数调用中,标准I/O操做是原子操做。
标准I/O最大的诟病是两次拷贝带来的性能开销。当读取数据时,标准I/O会向内核发起read系统调用,把数据从内核复制到标准I/O缓冲区,当应用经过标准I/O如fgetc发起读请求的时候,又会拷贝数据,此次是从标准I/O缓冲区拷贝到指定缓冲区。写入请求刚还相反,数据先从指定缓冲区拷贝到标准I/O缓冲区,而后又经过write函数,从标准I/O缓冲区写入内核。
分散汇集I/O是一种能够在单次系统调用中对多个缓冲区输入输出的方法,能够把多个缓冲区的数据写到单个数据流,也能够把单个数据流读到多个缓冲区。这种输入输出方法也称为向量I/O(vector I/O)。以前提到的标准读写系统调用能够称为线性I/O(linear I/O)。
向量I/O是经过readv和writev这个两个系统调用来实现的。
#include <sys/uio.h> ssize_t readv(int fd, const struct iovec *iov, int count); #include <sys/uio.h> ssize_t writev(int fd, const struct iovec *iov, int count);
除了操做多个缓冲区以外,readv和writev功能和read,write功能一致。
每一个iovec结构体描述一个独立的,物理不连续的缓冲区,咱们称之为段(segment):
#include <sys/uio.h> struct iovec{ void *iov_base; /* pointer to start of buffer */ size_t iov_len; /* size of buffer in bytes */ }
一组段的集合称为向量(vector)。每一个段描述了内存中须要读写的缓冲区地址和长度。readv函数在处理下个缓冲区以前,会填满当前缓冲区的iov-len个字节。writev函数在处理下个缓冲区以前也会把当前缓冲区全部iov_len个字节输出。这两个函数都会顺序处理向量中的段,从iov[0]开始,接着iov[1],一直到iov[count -1]。
Linux内核中全部的I/O都是向量I/O,read和write是做为向量I/O实现的,且向量中只有一个段。
条件触发是默认行为,poll和select就是这种模式。
内核支持应用程序将文件映射到内存中,即内存地址和文件数据一一对应。这样开发人员就能够直接经过内存来访问文件,Linux实现了POSIX.1标准中定义的mmap()系统调用。
#include <sys/mman.h> void * mmap (void *addr, size_t len, int prot, int flags, int fd, off_t offset);
Linux提供了系统调用madvise,进程对本身指望如何访问映射区域给内核一些提示信息。
I/O调度器实现两个基本操做,合并(merging)和排序(sorting)。
合并是讲两个或者多个相邻的I/O请求合并为一个。
排序是选取两个操做中相对重要的一个,幷按块号递增的顺序从新安排等待的I/O请求。如5,20,7这样,排序后就变成5,7,20。若是这个时候有6的访问,这个就会被插入到5和7之间。
若是只是对请求进行排序的话,如一直访问50-60之间的那么100的请求就会一直得不到调度。
处理这个问题的方法就是Linus电梯调度算法,在该方法中若是队列有必定数量的旧的请求,则中止新的请求。这样虽然总体上能够作到平等对待每一个请求,但在读的时候却增长了读延迟。这个算法是Linux2.4内核使用的,在2.6废弃了,使用了几种新的调度器算法。
这个算法就是在电梯算法上添加了两个队列,分别为读队列和写队列。每一个请求到了之后除了加到标准队列,还要加到相应队列的队尾,每一个请求都设置了过时时间,读的为200毫秒,写的是5秒。当读写的两个队列的队首超出了过时时间,调度器就会中止从标准队列中处理请求,转而处理相应队列队首的请求。
这个调度器是deadline的改进版(就是多了一个预测机制),也是三个队列,可是这个调度器会在每一个请求到达过时时间以前调度它,不会像deadline那样立刻调度它而是等待6毫秒。若是这段时间还有对硬盘同一部分发起请求,这个请求就会马上响应,Anticipatory 调度器会继续等待。若是6毫秒内没有收到请求,调度器就会认为预测失败,而后返回正常操做(如处理标准队列中断的请求)。
CFQ意为Complete Fair Queuing,这个调度器中,每一个进程都有本身的队列,每一个队列分配一个时间片。调度程序轮询方式处理队列中的请求。知道队列的时间片耗尽或者全部的请求都处理完(若是是全部请求处理完以后会等待一段时间默认10毫秒缘由和Anticipatory调度器的预测机制同样)。
CFQ I/O调度器适合大多数场景。
这个是最简单的调度算法,它只进行合并不作排序。SSD大部分使用这种调度器。
有篇文章推荐下:http://orababy.blogspot.com/2014/06/best-io-schedulerelevator-for-oracle.html
要选择调度器能够修改文件/sys/block/device/queue/scheduler来修改。目录/sys/block/device/queue/iosched包含了调度器相关的选项。
个人虚拟机中的设置:
[root@node2 block]# pwd /sys/block [root@node2 block]# for i in `ls */queue/scheduler`; do echo $i;cat $i; done dm-0/queue/scheduler none dm-1/queue/scheduler none dm-2/queue/scheduler none fd0/queue/scheduler noop [deadline] cfq sda/queue/scheduler noop [deadline] cfq sr0/queue/scheduler noop deadline [cfq] [root@node2 iosched]# pwd /sys/block/sda/queue/iosched [root@node2 iosched]# for i in `ls`; do echo $i;cat $i; done fifo_batch 16 front_merges 1 read_expire 500 write_expire 5000 writes_starved 2
/sys 和/proc会继续共存
sysfs文件系统的目的是要展示设备驱动程序模型组件间的关系,该文件系统的相应高层目录
在最初的PC体系结构中,cpu是系统惟一的总线控制器,也就是说为了提取和存储ram存储单元的值,cpu是惟一能够驱动地址/数据总线的硬件设备。
设备驱动程序能够采用两种方式使用DMA,同步DMA是由进程触发数据的传送(声卡播放音乐),异步DMA是由硬件设备触发数据的传送(网卡收到lan的帧,保存到本身的IO共享存储器中,而后引起一个中断,其驱动程序确认该中断后,命令网卡将收到的帧从IO共享存储器拷贝到内核缓冲区,当数据传送完成后,网卡会引起新的中断,而后驱动程序将这个新帧通知给上层内核层)。