分类 Linux内核 下的文章

理解TCP之Keepalive

理解Keepalive(1)

大家都听过keepalive,但是其实对于keepalive这个词还是很晦涩的,至少我一直都只知道一个大概,直到之前排查线上一些问题,发现keepalive还是有很多玄机的。其实keepalive有两种,一种是TCP层的keepalive,另一种是HTTP层的Keep-Alive。这篇文章先说说tcp层的keepalive

tcp keepalive

设想有一种场景:A和B两边通过三次握手建立好TCP连接,然后突然间B就宕机了,之后时间内B再也没有起来。如果B宕机后A和B一直没有数据通信的需求,A就永远都发现不了B已经挂了,那么A的内核里还维护着一份关于A&B之间TCP连接的信息,浪费系统资源。于是在TCP层面引入了keepalive的机制,A会定期给B发空的数据包,通俗讲就是心跳包,一旦发现到B的网络不通就关闭连接。这一点在LVS内尤为明显,因为LVS维护着两边大量的连接状态信息,一旦超时就需要释放连接。

Linux内核对于tcp keepalive的调整主要有以下三个参数

1. tcp_keepalive_time

 the interval between the last data packet sent (simple ACKs are not considered data) and the first keepalive probe; after the connection is marked to need keepalive, this counter is not used any further

2. tcp_keepalive_intvl

 the interval between subsequential keepalive probes, regardless of what the connection has exchanged in the meantime

3. tcp_keepalive_probes

 the number of unacknowledged probes to send before considering the connection dead and notifying the application layer

Example

$ cat /proc/sys/net/ipv4/tcp_keepalive_time
  7200
$ cat /proc/sys/net/ipv4/tcp_keepalive_intvl
  75
$ cat /proc/sys/net/ipv4/tcp_keepalive_probes
  9

当tcp发现有tcp_keepalive_time(7200)秒未收到对端数据后,开始以间隔tcp_keepalive_intvl(75)秒的频率发送的空心跳包,如果连续tcp_keepalive_probes(9)次以上未响应代码对端已经down了,close连接

在socket编程时候,可以调用setsockopt指定不同的宏来更改上面几个参数

TCP_KEEPCNT: tcp_keepalive_probes

TCP_KEEPIDLE: tcp_keepalive_time

TCP_KEEPINTVL: tcp_keepalive_intvl

- 阅读剩余部分 -

理解Linux文件系统挂载参数noatime nodiratime

很多线上服务器为了提供文件系统IO性能,会在挂载文件系统的时候指定“noatime,nodiratime”参数,意味着当访问一个文件和目录的时候,access time都不会更新。但是如果未指定上面的参数,atime则会更新。那么具体差异在哪里?

未指定 noatime,nodiratime

$ touch test ; stat test ;
...
Access: 2015-04-04 00:37:23.507135507 +0800
Modify: 2015-04-04 00:37:23.507135507 +0800
Change: 2015-04-04 00:37:23.507135507 +0800

$ echo hello >> test ; stat test;
...
Access: 2015-04-04 00:37:23.507135507 +0800
Modify: 2015-04-04 00:37:38.018430637 +0800
Change: 2015-04-04 00:37:38.018430637 +0800

$ cat test ;stat test
...
Access: 2015-04-04 00:38:02.916135510 +0800
Modify: 2015-04-04 00:37:38.018430637 +0800
Change: 2015-04-04 00:37:38.018430637 +0800

可以看出未指定"noatime,nodiratime"的情况下

  1. read文件的时候会导致atime更新,不会导致mtime和ctime更新
  2. write文件只会导致mtime和ctime更新,不会导致atime更新。

指定 noatime,nodiratime

$touch test ; stat test ; 
...
Access: 2015-04-04 00:28:28.680135484 +0800
Modify: 2015-04-04 00:28:28.680135484 +0800
Change: 2015-04-04 00:28:28.680135484 +0800

$ sleep 10 ; echo hello >> test ; stat test;
...
Access: 2015-04-04 00:28:28.680135484 +0800
Modify: 2015-04-04 00:28:38.682727983 +0800
Change: 2015-04-04 00:28:38.682727983 +0800

$ cat test ;stat test
...
Access: 2015-04-04 00:28:28.680135484 +0800
Modify: 2015-04-04 00:28:38.682727983 +0800
Change: 2015-04-04 00:28:38.682727983 +0800

可以看出指定"noatime,nodiratime"的情况下

  1. read文件的时候不会导致atime、mtime、ctime改变
  2. write文件只会导致mtime和ctime更新,不会导致atime更新。

实际应用场景

在平日里经常有删除文件的需求,大概如下

删除过去N天内都未访问过的文件或者目录(删除N天前访问过的文件)

$ #注意这条命令很危险! 
$ find /home/fire/ -atime +N -exec rm -rf {} \;

假设 /home/fire 目录是一周之前创建的,那么对于这条命令有两个执行结果

$ #注意这条命令很危险! 
$ find /home/fire/ -atime +7 -exec rm -rf {} \;
  • 指定"noatime":find的时候发现 /home/fire 是7天之前创建的,立马就会删除整个目录。而且还会报错"find: /home/fire: No such file or directory",原因就是第一个rm -rf /home/fire 之后 find失败了。这种是很危险的!原因是会误删除文件。
  • 未指定"noatime":那就得看情况,如果/home/fire过去7天没有被访问过,那么就和情况一一样,直接删除。如果过去7天内,该目录有人访问过,atime肯定是7天之内,那么就会遍历下面的目录,依次按照之前逻辑。但是遍历过程会更改目录的atime。

看了上面的例子会发现find去删除目录的时候变得好复杂,而且一定要小心。所以find删除更适用于删除文件,不要删除目录。

删除N天内未被访问过的文件
$ find /home/fire/ -atime +N -type f -exec rm -f {} \;

Linux的overcommit配置

Linux对大部分申请内存的请求都回复"yes",以便能跑更多更大的程序。因为申请内存后,并不会马上使用内存。这种技术叫做Overcommit。

当内存不足时,会发生OOM killer(OOM=out-of-memory)。它会选择杀死一些进程(用户态进程,不是内核线程),以便释放内存。

Overcommit和下面两个vm的配置有关系。

vm.overcommit_ratio 
vm.overcommit_memory

- 阅读剩余部分 -

Linux read函数深入

之前Linux read函数浅析-高速缓存说到Linux下读写文件都是通过高速缓存区实现的,应用程序要和底层的设备打交道,首先是通过高速缓存区,要是所要读的数据不存在,高速缓存区会帮应用程序代理去“拿”数据,高速缓存区的处理程序会向设备的驱动程序发出读请求,等待数据返回,这里就说说高速缓存区怎么实现和底层设备驱动函数交互。

- 阅读剩余部分 -

Linux read函数浅析-高速缓存

最近一直在看Linux下read()函数的实现,发现确实是复杂,几次想写篇博客来把read()函数讲清楚,可是写着写着就断了思绪,有些地方不是很懂,有些概念太过于模糊。今天还是决定写下,只能大概讲讲read()函数。

高速缓存


首先引入的就是高速缓存,什么是高速缓存?众所周知,磁盘的I/O和内存的I/O两者访问是相差几个数量级的,A进程访问了X文件,Linux为了加速A进程或者其他进程再次访问X文件的时候不再千里迢迢到去磁盘上寻找,于是就设计了高速缓存,把A进程访问过的X文件缓存在内存里面。这么说高速缓存就是内存了?对,高速缓存就是属于物理内存的一部分!Linux设计思想就是尽可能地发挥物理内存的使用。

Linux读写机制


好,我们看看高速缓存怎么发挥作用,A进程要去磁盘上访问磁盘上的X文件,首先会在高速缓存区里面找,找到了就直接返回对应的内容,找不到呢?没办法,得再去磁盘上找,通过设备驱动程序把相应的文件内容读取到高速缓存里,然后A进程从高速缓存里得到数据,返回给用户空间。那么某个进程再去请求X文件的时候就会直接从高速缓存区里返回了,效率就会大大提高。
那么对于写文件呢?A进程要写磁盘文件,那么先把数据写到高速缓存区里面,并不是写了小部分数据就会立马同步到磁盘上的。内核会有一组回写线程,一段时间去把高速缓存区上“脏”的逻辑块写到磁盘上,这里“脏”的意思和虚拟内存页差不多,最简单定义就是内存中某个块和磁盘上对应块不一样就回写(不是页)。

高速缓存实现


对于块设备而言,比如磁盘,最小的访问单位都是扇区,一般一个扇区是512字节,Linux抽象了一层块的概念,平时说的I/O块和逻辑块都是这个意思,一个块必然是扇区的整数倍,一般都是1K。在内核0.11里面,高速缓存区都是这些块组成的,统一用buffer_head进行描述,所有的块都组成双向链表。每个设备号+逻辑块号还通过指定的HASH算法构成自己的链表,访问X文件会在inode节点里提供设备号,接下来需要的是逻辑块号的获取是通过提供的文件偏移地址算出来的,就可以在对应的设备号+逻辑块号HASH队列里找到相应缓存块,找不到就向底层的驱动程序发出读磁盘请求。块的概念都是面向设备的,也就是驱动程序和设备打交道的最基本单位都是块。而对于应用程序和高速缓存区打交道都是基于页的概念,页是在块纸上又一层抽象,一个页一般是4K,也就是4个块。下面这张图可以详细说明这些概念之间的关系。

buffer

总结


进程要和块设备打交道都是需要经过高速缓存的(也有特殊情况)进程发起读请求,首先会有缓存区页面管理程序接收,它会根据逻辑块号和设备号去高速缓存区找请求的buffer,找到就直接返回;如果找不到,缓存区页面管理程序会向底层的驱动程序发出读指令,等其放回后缓存到高速缓存里,然后返回给应用进程。至于高速缓存区的维护则是由内核维护,淘汰算法也是LRU。

Linux虚拟内存概述

为什么需要虚拟内存?


程序是一系列代码段,数据段的集合,而程序要运行必须是加载到内存里的,但是物理内存就那么大,如何能保证很多个程序都装载进去呢?这里就引进了虚拟内存的概念,虚拟内存基本思想就是,给每个程序都分配一个4G的虚拟的内存,但这部分内存占用的不是物理内存,而是磁盘空间,这部分叫做虚拟存储器,就是安装Linux系统时候的SWAP空间。而对应的物理内存就是物理存储器。
有没有觉得整个过程像开了“空头支票”一样?程序要跑起来,操作系统许诺它给你4G的空间,但却是不能用的,那真要执行的时候怎么办呢?操作系统会把磁盘上的程序代码数据“移”到内存里,把不需要的还会“移”出去到磁盘上,这样看上去就好像可以跑很多进程了。

- 阅读剩余部分 -

Linux系统调用

最近重新开始看操作系统,看得我云里雾里,经典的操作系统书又很难,看不大懂。学校的操作系统课,说实在的,真的好难和自己真正编程结合起来,里面的概念很多,而且很多都是很老的,历史总是这么蛋疼。没办法,只能硬着头皮去看。这次先讲讲系统调用,因为没看过里面的具体源码,但是尽可能让文章偏向实际编程中的应用。

什么叫系统调用


计算机有很多的硬件,比如键盘,鼠标,网卡,磁盘,声卡这些硬件都是我们实际可以触碰到的,但是对于用户应用程序而言是没办法直接操作这些设备的。为什么呢?一是硬件涉及到很多电路实现,让用户自己编写程序去操作是很困难和复杂的。二是编程的人直接操作硬件会有很大的安全隐患,因为这些资源都是很多程序共享的,万一我们写的程序能够直接操作硬件,安全性就不能保障了。所以所有这些设备的上面抽象了一层驱动程序,这些驱动程序会实现很多硬件相关操作,也就是说每个硬件发布商都得提供各自的硬件的驱动程序用于操作硬件。那么应用程序怎么去调用这些驱动程序呢?答案就是通过操作系统的系统调用,我们告诉操作系统我们要对某个硬件做某个操作,操作系统来帮你完成,然后把得到的结果返回给我们。

- 阅读剩余部分 -

第一个内核程序"Hello World"

内核模块可以用insmod来动态加载,用rmmod来动态卸载.与应用层编程的不同在于,内核模块需要一个

初始化函数和一个清理函数,在向内核加载模块的时候调用初始化函数,卸载模块的时候调用清理函数.与

传统把函数编译进内核相比,通过模块的形式大大提高了开发的效率.

下面是内核版本的Hello World,先看代码

- 阅读剩余部分 -