进程读写文件之前需要 打开文件 ,得到 文件描述符 ,然后 通过文件描述符读写文件 .
内核提供了两个打开文件的系统调用 open 和 openat .
打开文件的主要步骤如下:
(1)需要 在父目录的数据中查找文件对应的目录项 , 从目录项得到索引节点的编号,然后在内存中创建索引节点的副本 .因为各种文件系统类型的物理结构不同,所以需要提供索引节点 *** 作集合的 lookup 方法和文件 *** 作集合的 open 方法.
(2)需要分配文件的一个打开实例-- file 结构体,关联到文件的索引节点.
(3)在进程的打开文件表中 分配一个文件描述符 , 把文件描述符和打开实例的映射添加到进程的打开文件表 中.
进程可通过使用系统调用 close 关闭文件.
系统调用close的执行流程如下:
(1)解除打开文件表和file实例的关联.
(2)在close_on_exec位图中清楚文件描述符对应的位.
(3)释放文件描述符,在文件描述符位图中清除文件描述符对应的位.
(4)调用函数fput释放file实例:把引用计数减1,如果引用计数是0,那么把file实例添加到链表delayed_fput_list中,然后调用延迟工作项delayed_fput_work.
延迟工作项delayed_fput_work的处理函数是flush_delayed_fput,遍历链表delayed_fput_list,针对每个file实例,调用函数__fput来加以释放.
创建不同类型的文件,需要使用不同的命令.
(1) 普通文件 :touch FILE ,这条命令本来用来更新文件的访问时间和修改时间,如果文件不存在,创建文件.
(2) 目录 :mkdir DIRECTORY .
(3) 符号链接(软链接) :ln -s TARGET LINK_NAME 或ln --symbolic TARGET LINK_NAME .
(4) 字符或块设备文件 :mknod NAME TYPE [MAJOR MINOR] .
(5) 命名管道 :mkpipe NAME .
(6) 硬连接 :命令"ln TARGET LINK_NAME ".给已经存在的文件增加新的名称,文件的索引节点有一个硬链接计数,如果文件有n个名称,那么硬链接计数是n.
创建文件需要在文件系统中 分配一个索引节点 ,然后 在父目录的数据中增加一个目录项来保存文件的名称和索引节点编号 .
删除文件的命令如下:
(1)删除任何类型文件:unlink FILE .
(2)rm FILE ,默认不删除目录,如果使用"-r""-R"或"-recursive",可以删除目录和目录的内容.
(3)删除目录:rmdir DICTIONARY .
内核提供了unlink,unlinkat用来删除文件的名称,如果文件的硬链接计数变成0,并且没有进程打开这个文件,那么删除文件.提供了rmdir删除目录.
删除文件需要从父目录的数据中删除文件对应的目录项, 把文件的索引节点的硬链接计数减1(一个文件可以有多个名称,Linux把文件名称称为硬链接),如果索引节点的硬链接计数变成0,那么释放索引节点 .因为各种文件系统的物理结构不同,所以需要提供索引节点 *** 作集合的 unlink 方法.
设置文件权限的命令如下:
(1)chmod [OPTION]... MODE[, MODE]... FILE...
mode : 权限设定字串,格式[ugoa...][[+-=][rwxX]...][,...]
其中:
(2)chmod [OPTION]... OCTAL-MODE FILE...
参数OCTAL-MODE是八进制数值.
系统调用chmod负责修改文件权限.
修改文件权限需要修改文件的索引节点的文件模式字段,文件模式字段包含文件类型和访问权限.因为各种文件系统类型的索引节点不同,所以需要提供索引节点 *** 作集合的 setattr 方法.
访问外部存储设备的速度很慢,为了避免每次读写文件时访问外部存储设备, 文件系统模块为每个文件在内存中创建一个缓存 ,因为 缓存的单位是页 ,所以称为 页缓存 .
(1) 索引节点的成员i_mapping 指向地址空间结构体(address_space).进程在打开文件的时候, 文件打开实例(file结构体)的成员f_mapping 也会指向文件的地址空间.
(2)每个文件有一个地址空间结构体 address_space ,成员 page_tree 的类型是结构体radix_tree_root:成员 gfp_mask是分配内存页的掩码,成员rnode指向基数树的根节点 .
(3)使用基数树管理页缓存,把文件的页索引映射到内存页的页描述符.
每个文件都有一个地址空间结构体address_space,用来建立数据缓存(在内存中为某种数据创建的缓存)和数据来源(即存储设备)之间的关联.结构体address_space如下:
地址空间 *** 作结合address_space_operations的主要成员如下:
页缓存的常用 *** 作函数如下:
(1)函数find_get_page根据文件的页索引在页缓存中查找内存页.
(2)函数find_or_create_page根据文件的页索引在页缓存中查找内存页,如果没有找到内存页,那么分配一个内存页,然后添加到页缓存中.
(3)函数add_to_page_cache_lru把一个内存页添加到页缓存和LRU链表中.
(4)函数delete_from_page_cache从页缓存中删除一个内存页.
进程读文件的方式有3种:
(1)调用内核提供的 读文件的系统调用 .
(2)调用glibc库封装的读文件的 标准I/O流函数 .
(3)创建基于文件的内存映射,把 文件的一个区间映射到进程的虚拟地址空间,然后直接读内存 .
第2种方式在用户空间创建了缓冲区,能减少系统调用的次数,提高性能.第3种方式可以避免系统调用,性能最高.
读文件的主要步骤如下:
(1)调用具体文件系统类型提供的文件 *** 作集合的read和read_iter方法来读文件.
(2) read或read_iter方法根据页索引在文件的页缓存中查找页,如果没有找到,那么调用具体文件系统类型提供的地址空间集合的readpage方法来从存储设备读取文件页到内存中 .
为了提高读文件的速度,从存储设备读取文件页到内存中的时候,除了读取请求的文件页,还会预读后面的文件页.如果进程按顺序读文件,预读文件页可以提高读文件的速度如果进程随机读文件,预读文件页对提高读文件的速度帮助不大.
进程写文件的方式有3种:
(1)调用内核提供的 写文件的系统调用 .
(2)调用glibc库封装的写文件的 标准I/O流函数 .
(3)创建基于文件的内存映射,把 文件的一个区间映射到进程的虚拟空间,然后直接写内存 .
第2种方式在用户空间创建了缓冲区,能够减少系统调用的次数,提高性能.第3种方式可以避免系统调用,性能最高.
写文件的主要步骤如下:
(1)调用具体文件系统类型提供的文件 *** 作集合的write或write_iter方法来写文件.
(2)write或write_iter方法调用文件的地址空间 *** 作集合的 write_begin 方法, 在页缓存查找页,如果页不存在就分配页然后把数据从用户缓冲区复制到页缓存的页中 最后调用文件的地址空间 *** 作集合的 write_end 方法.
进程写文件时,内核的文件系统模块把数据写到文件的页缓存,没有立即写回到存储设备.文件系统模块会定期把脏页写回到存储设备,进程也可以调用系统调用把脏页强制写回到存储设备.
管理员可以执行命令"sync",把内存中所有修改过的文件元数据和文件数据写回到存储设备.
内核提供了 sync , syncfs , fsync , fdatasync , sync_file_range 等系统调用用于文件写回.
把文件写回到存储设备的时机如下:
(1)周期回写.
(2)当脏页的数量达到限制的时候,强制回写.
(3)进程调用sync和syncfs等系统调用.
对于类似内存的块设备,例如NVDIMM设备,不需要把文件从存储设备复制到页缓存.DAX绕过页缓存,直接访问存储设备,对于基于文件的内存映射,直接把存储设备映射到进程的虚拟地址空间.
调用系统调用mmap创建基于文件的内存映射,把文件的一个区间映射到进程的虚拟地址空间,这会调用具体文件系统类型提供的文件 *** 作集合的mmap方法.mmap方法针对设置了标志位S_DAX的索引节点,处理方法如下:
(1)给虚拟内存区域设置标志位VM_MIXEDMAP和VM_HUGEPAGE.
(2)设置虚拟内存 *** 作集合,提供fault,huge_fault,page_mkwrite和pfn_mkwrite方法.
作为高性能WEB服务器,只调整Nginx本身的参数是不行的,因为Nginx服务依赖于高性能的 *** 作系统。
以下为常见的几个Linux内核参数优化方法。
net.ipv4.tcp_max_tw_buckets
对于tcp连接,服务端和客户端通信完后状态变为timewait,假如某台服务器非常忙,连接数特别多的话,那么这个timewait数量就会越来越大。
毕竟它也是会占用一定的资源,所以应该有一个最大值,当超过这个值,系统就会删除最早的连接,这样始终保持在一个数量级。
这个数值就是由net.ipv4.tcp_max_tw_buckets这个参数来决定的。
CentOS7系统,你可以使用sysctl -a |grep tw_buckets来查看它的值,默认为32768,
你可以适当把它调低,比如调整到8000,毕竟这个状态的连接太多也是会消耗资源的。
但你不要把它调到几十、几百这样,因为这种状态的tcp连接也是有用的,
如果同样的客户端再次和服务端通信,就不用再次建立新的连接了,用这个旧的通道,省时省力。
net.ipv4.tcp_tw_recycle = 1
该参数的作用是快速回收timewait状态的连接。上面虽然提到系统会自动删除掉timewait状态的连接,但如果把这样的连接重新利用起来岂不是更好。
所以该参数设置为1就可以让timewait状态的连接快速回收,它需要和下面的参数配合一起使用。
net.ipv4.tcp_tw_reuse = 1
该参数设置为1,将timewait状态的连接重新用于新的TCP连接,要结合上面的参数一起使用。
net.ipv4.tcp_syncookies = 1
tcp三次握手中,客户端向服务端发起syn请求,服务端收到后,也会向客户端发起syn请求同时连带ack确认,
假如客户端发送请求后直接断开和服务端的连接,不接收服务端发起的这个请求,服务端会重试多次,
这个重试的过程会持续一段时间(通常高于30s),当这种状态的连接数量非常大时,服务器会消耗很大的资源,从而造成瘫痪,
正常的连接进不来,这种恶意的半连接行为其实叫做syn flood攻击。
设置为1,是开启SYN Cookies,开启后可以避免发生上述的syn flood攻击。
开启该参数后,服务端接收客户端的ack后,再向客户端发送ack+syn之前会要求client在短时间内回应一个序号,
如果客户端不能提供序号或者提供的序号不对则认为该客户端不合法,于是不会发ack+syn给客户端,更涉及不到重试。
net.ipv4.tcp_max_syn_backlog
该参数定义系统能接受的最大半连接状态的tcp连接数。客户端向服务端发送了syn包,服务端收到后,会记录一下,
该参数决定最多能记录几个这样的连接。在CentOS7,默认是256,当有syn flood攻击时,这个数值太小则很容易导致服务器瘫痪,
实际上此时服务器并没有消耗太多资源(cpu、内存等),所以可以适当调大它,比如调整到30000。
net.ipv4.tcp_syn_retries
该参数适用于客户端,它定义发起syn的最大重试次数,默认为6,建议改为2。
net.ipv4.tcp_synack_retries
该参数适用于服务端,它定义发起syn+ack的最大重试次数,默认为5,建议改为2,可以适当预防syn flood攻击。
net.ipv4.ip_local_port_range
该参数定义端口范围,系统默认保留端口为1024及以下,以上部分为自定义端口。这个参数适用于客户端,
当客户端和服务端建立连接时,比如说访问服务端的80端口,客户端随机开启了一个端口和服务端发起连接,
这个参数定义随机端口的范围。默认为32768 61000,建议调整为1025 61000。
net.ipv4.tcp_fin_timeout
tcp连接的状态中,客户端上有一个是FIN-WAIT-2状态,它是状态变迁为timewait前一个状态。
该参数定义不属于任何进程的该连接状态的超时时间,默认值为60,建议调整为6。
net.ipv4.tcp_keepalive_time
tcp连接状态里,有一个是established状态,只有在这个状态下,客户端和服务端才能通信。正常情况下,当通信完毕,
客户端或服务端会告诉对方要关闭连接,此时状态就会变为timewait,如果客户端没有告诉服务端,
并且服务端也没有告诉客户端关闭的话(例如,客户端那边断网了),此时需要该参数来判定。
比如客户端已经断网了,但服务端上本次连接的状态依然是established,服务端为了确认客户端是否断网,
就需要每隔一段时间去发一个探测包去确认一下看看对方是否在线。这个时间就由该参数决定。它的默认值为7200秒,建议设置为30秒。
net.ipv4.tcp_keepalive_intvl
该参数和上面的参数是一起的,服务端在规定时间内发起了探测,查看客户端是否在线,如果客户端并没有确认,
此时服务端还不能认定为对方不在线,而是要尝试多次。该参数定义重新发送探测的时间,即第一次发现对方有问题后,过多久再次发起探测。
默认值为75秒,可以改为3秒。
net.ipv4.tcp_keepalive_probes
第10和第11个参数规定了何时发起探测和探测失败后再过多久再发起探测,但并没有定义一共探测几次才算结束。
该参数定义发起探测的包的数量。默认为9,建议设置2。
设置和范例
在Linux下调整内核参数,可以直接编辑配置文件/etc/sysctl.conf,然后执行sysctl -p命令生效。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)