Youmai の Blog


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

CLOSE_WAIT

发表于 2015-03-06 | 分类于 TCP/IP

关闭socket分为主动关闭(Active closure)和被动关闭(Passive closure)两种情况。前者是指有本地主机主动发起的关闭;而后者则是指本地主机检测到远程主机发起关闭之后,作出回应,从而关闭整个连接。

前者将会产生TIME_WAIT,而后者,则会产生CLOSE_WAIT

##产生原因

服务器A是一台爬虫服务器,它使用简单的HttpClient去请求资源服务器B上面的apache获取文件资源,正常情况下,如果请求成功,那么在抓取完资源后,服务器A会主动发出关闭连接的请求,这个时候就是主动关闭连接,服务器A的连接状态我们可以看到是TIME_WAIT。如果一旦发生异常呢?假设请求的资源服务器B上并不存在,那么这个时候就会由服务器B发出关闭连接的请求,服务器A就是被动的关闭了连接,如果服务器A被动关闭连接之后程序员忘了让HttpClient释放连接,那就会造成CLOSE_WAIT的状态了。

在被动关闭连接情况下,在已经接收到FIN,但是还没有发送自己的FIN的时刻,连接处于CLOSE_WAIT状态。

通常来讲,CLOSE_WAIT状态的持续时间应该很短,正如SYN_RCVD状态。但是在一些特殊情况下,就会出现连接长时间处于CLOSE_WAIT状态的情况。

出现大量close_wait的现象,主要原因是某种情况下对方关闭了socket链接,但是我方忙与读或者写,没有关闭连接。代码需要判断socket,一旦读到0,断开连接,read返回负,检查一下errno,如果不是AGAIN,就断开连接。

##解决方法

基本的思想就是要检测出对方已经关闭的socket,然后关闭它。

  1. 代码需要判断socket,一旦read返回0,断开连接,read返回负,检查一下errno,如果不是AGAIN,也断开连接。(注:在UNP 7.5节的图7.6中,可以看到使用select能够检测出对方发送了FIN,再根据这条规则就可以处理CLOSE_WAIT的连接)

  2. 给每一个socket设置一个时间戳last_update,每接收或者是发送成功数据,就用当前时间更新这个时间戳。定期检查所有的时间戳,如果时间戳与当前时间差值超过一定的阈值,就关闭这个socket。

  3. 使用一个Heart-Beat线程,定期向socket发送指定格式的心跳数据包,如果接收到对方的RST报文,说明对方已经关闭了socket,那么我们也关闭这个socket。

  4. 设置SO_KEEPALIVE选项,并修改内核参数(前提是启用socket的KEEPALIVE机制)

##如果服务器出了问题

如果服务器出了异常,百分之八九十都是下面两种情况:

  1. 服务器保持了大量TIME_WAIT状态
  2. 服务器保持了大量CLOSE_WAIT状态

因为linux分配给一个用户的文件句柄是有限的,而TIME_WAIT和CLOSE_WAIT两种状态如果一直被保持,那么意味着对应数目的通道就一直被占着,而且是“占着茅坑不使劲”,一旦达到句柄数上限,新的请求就无法被处理了,接着就是大量Too Many Open Files异常,tomcat崩溃。。。

网上有很多资料把这两种情况的处理方法混为一谈,以为优化系统内核参数就可以解决问题,其实是不恰当的,优化系统内核参数(/etc/sysctl.conf)解决TIME_WAIT可能很容易,但是应对CLOSE_WAIT的情况还是需要从程序本身出发。

listen函数的backlog参数

发表于 2015-03-06 | 分类于 unix网络编程

listen函数将主动套接字转换为被动监控套接字,其第二个参数backlog决定了内核的连接缓存队列长度。对于一个给定的监听套接字,内核维护两个队列:

  1. 未就绪队列,存放没有完成三路握手的连接,监听套接字收到SYN并返回ACK+SYN,连接处于SYN_RECV状态,等待对端发送ACK。如果已完成队列非满,则接收ACK,连接握手完成,进入已完成队列;如果已完成队列满则丢弃ACK,对端重发ACK(对端看到的连接是ESTABLISED状态),若未就绪队列中的SYN_RECV等待直到超时还没进入已完成队列则丢弃连接(对端不知道,只有在读写套接字时才知道)。

  2. 已完成队列,存放已经完成三路握手的连接(ESTABLISHED),等待accept取走连接。

tcp为监听套接字维护的两个队列

backlog决定了两个队列的长度之和(并不是说两个队列之和等于backlog,而是存在个转换,依赖于具体实现)

实现strcpy

发表于 2015-03-06 | 分类于 c

##strcpy和memcpy
很容易写出下面的实现

char *my_strcpy(char *dst,const char *src)
{
    if(dst == NULL || src == NULL)
        return NULL;
    char *ret = dst;
    while((* dst++ = * src++) != '\0') 
        ;
    return ret;
}

上面的代码已经考虑到了

  1. 检查指针有效性;

  2. 返回目的指针des;

  3. 源字符串的末尾 ‘\0’ 需要拷贝。

但是这个字符串拷贝函数有很大缺陷

  1. 不能保证目标字符串有足够空间存放src

  2. 如果dst和src内存重叠,那输出是未定义的

但是即使是系统实现的strcpy好像也没有改正这两个bug。

在linux上man strcpy得到描述是:

DESCRIPTION

The strcpy() function copies the string pointed to by src, including the terminat-
ing null byte (‘\0’), to the buffer pointed to by dest. The strings may not over-
lap, and the destination string dest must be large enough to receive the copy.

The strncpy() function is similar, except that at most n bytes of src are copied.
Warning: If there is no null byte among the first n bytes of src, the string placed
in dest will not be null terminated.

If the length of src is less than n, strncpy() pads the remainder of dest with null
bytes.

BUGS

If the destination string of a strcpy() is not large enough, then anything might happen. Overflowing fixed-length string buffers is a favorite cracker technique
for taking complete control of the machine. Any time a program reads or copies
data into a buffer, the program first needs to check that there is enough space.
This may be unnecessary if you can show that overflow is impossible, but be care-
ful: programs can get changed over time, in ways that may make the impossible pos-
sible.

strcpy和strncpy都需要程序员来保证dst有足够的空间来容纳将被拷贝的字符串,如果dst空间不足,得到的结果是未定义的;同样也需要程序员来保证他们的空间没有重叠。strncpy只是增加了一个拷贝长度的参数,针对这个参数也要做一些判断。

  • 如果src长度不足n,那么dst不足n的部分将会用null byte来补齐

    char* strncpy(char *dest, const char *src, size_t n){
        size_t i;
    
        for (i = 0 ; i < n && src[i] != '\0' ; i++)
            dest[i] = src[i];
        for ( ; i < n ; i++)
            dest[i] = '\0';
    
        return dest;
    }
    

strcpy是高危函数,因为它遇到’\0’才停止运行,遇到没有’\0’的字符串,它就会一直运行下去。建议使用strncpy代替strcpy,它接收一个最大长度的参数。strcmp和strncmp也是如此。

##memcpy

上面提出的缺陷1确实想不到好方法来解决(只能交给程序员了),但是缺陷2在memcpy中已经解决了。memcpy在实现的时候考虑到了内存重叠的问题。

char *my_memcpy(char *dst,const char *src,unsigned int count)
{
     if(dst == NULL || src == NULL) 
        return NULL;
     char * ret = dst;
     //源地址和目的地址不重叠,低字节向高字节拷贝
     if (dst <= src || dst >= (src + count))
     {
         while(count--)
         {
             *dst = *src;
             dst++;
             src++;
         }
     }
     //源地址和目的地址重叠,高字节向低字节拷贝
     //这个时候src字符串的值会被改变,声明为const只是说不能通过
     //src修改src指向的值。这里是通过dst改变了src的值,这是允许的。
     else       
     { 
         dst = dst + count - 1;
         src = src + count - 1; 
         while(count--) 
         {
             *dst = *src;
             dst--;
             src--;
         }
    }
    return ret;
}

##区别

  1. strcpy只能复制字符串,memcpy可以复制任何内容
  2. strcpy遇到'\0'就停止,memcpy一直复制到n个字符才停止,不管有没有'\0'
  3. strcpy不支持内存重叠,memcpy支持

从socket读数据时,socket缓存里的数据,可能超过用户缓存的长度,如何处理?

发表于 2015-03-06 | 分类于 unix网络编程

从socket读数据时,socket缓存里的数据,可能超过用户缓存的长度,如何处理? 例如,socket缓存有8kB的数据,而你的缓存只有2kB空间。

可以调用realloc(),扩大原有的缓存块尺寸。

但是临时申请内存的有一定性能损失。

这种情况要看接收缓存的方式。

  • 第一种方式: 使用100k的大接收缓存为例。

如果要等待数据,并进行解析。可能发生缓存不够的情况。此时只能扩充缓存,或先处理100k的数据,再接收新的数据。

  • 第二种方式: 使用缓存队列,分成8K大小的队列。

不存在接收缓存不够的情况。 除非用户解析已出错,使用数据接收、使用脱勾。 这种方式的代价是,可能需要将缓存队列再次拷贝、拼接成一块大的缓存,再进行解析。 而在本人的系统中,只需要将socket接收的数据再次原样分发给客户, 所以这种方案是最佳方案。

向socket发送数据时, 可能只发送了用户缓存里的一半,如何处理?例如,需要向socket发送8kB数据,返回值只有2kB发送成功。

记录缓存的偏移量。 下一次socket写事件时, 再从偏移的位置接着发送。

使用linux epoll模型,水平触发模式(Level-Triggered),当socket可写时,会不停的触发socket可写的事件,如何处理?

发表于 2015-03-06 | 分类于 unix网络编程

##第一种方法

  1. 当需要向socket写数据时,将该socket加入到epoll模型(epoll_ctl);等待可写事件。

  2. 接收到socket可写事件后,调用write()或send()发送数据。

  3. 当数据全部写完后, 将socket描述符移出epoll模型。

这种方式的缺点是: 即使发送很少的数据,也要将socket加入、移出epoll模型,有一定的操作代价。

##第二种方法

  1. 向socket写数据时,不将socket加入到epoll模型;而是直接调用send()发送;

  2. 只有当或send()返回错误码EAGAIN(系统缓存满),才将socket加入到epoll模型,等待可写事件后,再发送数据。

  3. 全部数据发送完毕,再移出epoll模型。

这种方案的优点:当用户数据比较少时,不需要epool的事件处理。

在高压力的情况下,性能怎么样呢?

对一次性直接写成功、失败的次数进行统计。如果成功次数远大于失败的次数, 说明性能良好。(如果失败次数远大于成功的次数,则关闭这种直接写的操作,改用第一种方案。同时在日志里记录警告)

##第三种方法

使用Edge-Triggered(边沿触发),这样socket有可写事件,只会触发一次。

可以在应用层做好标记。以避免频繁的调用 epoll_ctl( EPOLL_CTL_ADD, EPOLL_CTL_MOD)。 这种方式是epoll 的 man 手册里推荐的方式, 性能最高。但如果处理不当容易出错,事件驱动停止。

如果 select 返回可读,结果只读到 0 字节,这是什么情况?

发表于 2015-03-06 | 分类于 unix网络编程

对端关闭,可读,read 返回 0,表示已经读到文件末尾。

  1. read返回0的唯一条件是对方gracefully close了socket
  2. select > 0,表示套接字里有东西,
  3. read = 0,表示里面的东西是“对方关闭连接”。

当select出错时,会将接口置为可读又可写。这时就要通过判断select的返回值为-1来区分。

tcp并发服务器--区分监听套接字与已连接套接字

发表于 2015-03-05 | 分类于 unix网络编程

并发服务器中主服务器循环通过派生一个子进程来处理每个新的连接。如果一个子进程继续使用服务器众所周知的端口来服务一个长时间的请求,那将发生什么?让我们来看一个典型的序列。首先,在主机freebsd上启动服务器,该主机是多宿的,其IP地址为12.106.32.254和192.168.42.1。服务器在它的众所周知的端口(本例为21)上执行被动打开,从而开始等待客户的请求,如图1所示。

图1-TCP服务器在端口21上执行被动打开

我们使用记号{*:21, *:*}指出服务器的套接字对。服务器在任意本地接口(第一个星号)的端口21上等待连接请求。外地IP地址和外地端口都没有指定,我们用"*.*"来表示。我们称它为监听套接字(listening socket)。

我们用冒号来分割IP地址和端口号,因为这是HTTP的用法,其他地方也常见。netstat程序使用点号来分割IP地址和端口号,不过如此表示有时候会让人混淆,因为点号既用于域名(如freebsd.unpbook.com.21),也用于IPv4的点分十进制数记法(如12.106.32. 254.21)。

这里指定本地IP地址的星号称为通配(wildcard)符。如果运行服务器的主机是多宿的(如本例),服务器可以指定它只接受到达某个特定本地接口的外来连接。这里要么选一个接口要么选任意接口。服务器不能指定一个包含多个地址的清单。通配的本地地址表示”任意”这个选择。通配地址通过在调用bind之前把套接字地址结构中的IP地址字段设置成INADDR_ANY来指定。

稍后在IP地址为206.168.112.219的主机上启动第一个客户,它对服务器的IP地址之一12.106.32.254执行主动打开。我们假设本例中客户主机的TCP为此选择的临时端口为1500,如图2-12所示。图中在该客户的下方标出了它的套接字对。

图2-客户对服务器的连接请求

当服务器接收并接受这个客户的连接时,它fork一个自身的副本,让子进程来处理该客户的请求,如图3所示。

至此,我们必须在服务器主机上区分监听套接字和已连接套接字(connected socket)。注意已连接套接字使用与监听套接字相同的本地端口(21)。还要注意在多宿服务器主机上,连接一旦建立,已连接套接字的本地地址(12.106.32.254)随即填入。

图3-并发服务器让子进程处理客户

下一步我们假设在客户主机上另有一个客户请求连接到同一个服务器。客户主机的TCP为这个新客户的套接字分配一个未使用的临时端口,譬如说1501,如图4所示。服务器上这两个连接是有区别的:第一个连接的套接字对和第二个连接的套接字对不一样,因为客户的TCP给第二个连接选择了一个未使用的端口(1501)。

图4-第二个客户与同一个服务器的连接

通过本例应注意,TCP无法仅仅通过查看目的端口号来分离外来的分节到不同的端点。它必须查看套接字对的所有4个元素才能确定由哪个端点接收某个到达的分节。图4中对于同一个本地端口(21)存在3个套接字。如果一个分节来自206.168.112.219端口1500,目的地为12.106.32.254端口21,它就被递送给第一个子进程。如果一个分节来自206.168.112.219端口1501,目的地为12.106.32.254端口21,它就被递送给第二个子进程。所有目的端口为21的其他TCP分节都被递送给拥有监听套接字的最初那个服务器(父进程)。

建立tcp连接就好像打电话

发表于 2015-03-05 | 分类于 TCP/IP

建立TCP连接就好比一个电话系统[Nemeth 1997]。

  • socket函数等同于有电话可用。
  • bind函数是在告诉别人你的电话号码,这样他们可以呼叫你。
  • listen函数是打开电话振铃,这样当有一个外来呼叫到达时,你就可以听到。
  • connect函数要求我们知道对方的电话号码并拨打它。
  • accept函数发生在被呼叫的人应答电话之时。

由accept返回客户的标识(即客户的IP地址和端口号)类似于让电话机的呼叫者ID功能部件显示呼叫者的电话号码。然而两者的不同之处在于accept只在连接建立之后返回客户的标识,而呼叫者ID功能部件却在我们选择应答或不应答电话之前显示呼叫者的电话号码。如果使用域名系统DNS(见第11章),它就提供了一种类似于电话簿的服务。getaddrinfo类似于在电话簿中查找某个人的电话号码,getnameinfo则类似于有一本按照电话号码而不是按照用户名排序的电话簿。

《unix网络编程》-p32

unix的errno值

发表于 2015-03-05 | 分类于 unix网络编程
  1. 只要一个Unix函数中有错误发生,全局变量errno就被置为一个指明该错误类型的正值,函数本身则通常返回-1。

  2. errno的值只在函数发生错误时设置。如果函数不返回错误,errno的值就没有定义。errno的所有正数错误值都是常值,具有以“E”开头的全大写字母名字,并通常在头文件中定义。值0不表示任何错误。

  3. 在全局变量中存放errno值对于共享所有全局变量的多个线程并不适合。线程函数遇到错误时并不设置标准Unix的errno变量,而是把errno的值作为函数返回值返回给调用者。这意味着每次调用以pthread_开头的某个函数时,我们必须分配一个变量来存放函数返回值,以便在调用err_sys前把errno变量设置成该值。

Questions from tencent

发表于 2015-03-04 | 分类于 面试

##指针和引用的区别

###相同点:

  • 都是地址的概念;
    • 指针指向一块内存,它的内容是所指内存的地址;
    • 引用是某块内存的别名。
      ###区别:
  1. 指针是一个实体,而引用仅是个别名;
  2. 引用使用时无需解引用(*),指针需要解引用;
  3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
  4. 引用没有 const,指针有 const;
  5. 引用不能为空,指针可以为空;
  6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得
    到的是指针本身(所指向的变量或对象的地址)的大小;
  7. 指针和引用的自增(++)运算意义不一样;
  8. 从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。

##static关键字的作用

  1. 在函数体,局部的static变量。生存期为程序的整个生命周期;作用域却在函数体内(它在什么地方能被访问(空间))。 一个被声明为静态的变量在这一函数被调用过程中维持其值不变。因为它分配在静态存储区,函数调用结束后并不释放单元,但是在其它的作用域无法访问。当再次调用这个函数时,这个局部的静态变量还存在,而且作用在它的访问空间,因此访问到的是上次调用后的值。

  2. 在文件模块内(但在函数体外),一个被声明为静态的全局变量可以被模块内所有函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。限制静态全局变量的作用域。

  3. 在文件模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。函数默认情况下是全局函数,可以被任意其它文件模块调用。

##const实现机制,比如:const int i,是怎么做到i只可读的?

实现机制:这些在编译期间完成,对于内置类型,如int, 编译器可能使用常数直接替换掉对此变量的引用。而对于结构体不一定。

看下面的例子:

const int j=100;    
int *p=const_cast<int*>(&j);    
*p=200;    
cout<<j<<endl;    输出为什么是100呢?
cout<<*p<<endl; //输出是改过的200

编译器在优化代码时把cout<<j直接优化成cout<<100了,所以虽然p和&j的值一样,但cout<<j不再通过访问j的地址输出。(反汇编时也有看到直接把数字压栈push 100 )

这是因为,const型在压栈时,是使用的直接的数,就有点像C的#define a 100

##volatile的含义
变量可能在编译器的控制或监控之外改变,告诉编译器不要优化该变量,如被系统时钟更新的变量。

volatile 的本意是“易变的”, 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用 volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用 volatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用 volatile 声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)

volatile 的本质:

  • 编译器的优化:在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值 copy 到该寄存器中,以便保持一致。当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致。当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致。

  • volatile 应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人。

##动态链接和静态链接的区别

动态链接是指在生成可执行文件时不将所有程序用到的函数链接到一个文件,
因为有许多函数在操作系统带的dll文件中,当程序运行时直接从操作系统中找。
而静态链接就是把所有用到的函数全部链接到exe文件中。

动态链接是只建立一个引用的接口,而真正的代码和数据存放在另外的可执行模块中,在运行时再装入;而静态链接是把所有的代码和数据都复制到本模块中,运行时就不再需要库了。

##在C++程序中调用被C编译器编译过的函数,为什么要加extern“C”

C++语言支持函数重载,c语言不支持函数重载。函数被C++编译后在库中的名称与C语言不同。假设某个函数的原型是void foo(int x, int y).该函数被c编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字

C++提供了C连接交换指定符号extern"C"解决名字匹配问题

##判断一个数是否是2的n次方

设数为x
!(x & (x - 1))

##linux下统计一个文件中有多少个给定字符串
grep -o ‘while’ list.c | wc -l

##常问的几个linux命令

###查看cpu信息

cat /proc/cpuinfo
cat /proc/meminfo

###查看硬盘信息
df -lh

###查看网卡信息
dmesg | grep eth

###更常用的命令(显示系统核心版本号、名称、机器类型等)
uname -a

##ipcs命令
ipcs:检查系统上共享内存的分配

###用途
报告进程间通信设施状态。

ipcs 命令往标准输出写入一些关于活动进程间通信设施的信息。如果没有指定任何标志,ipcs 命令用简短格式写入一些关于当前活动消息队列、共享内存段、信号量、远程队列和本地队列标题。

Linux下ipcs指令的用法详解。ipcs是Linux下显示进程间通信设施状态的工具。可以显示消息队列、共享内存和信号量的信息。对于程序员可能更有用些,普通的系统管理员一般用不到此指令。

ipcrm:手动解除系统上共享内存的分配

用途: 删除消息队列、信号集、或者共享内存标识。

##文件描述符fd
fd只是一个整数,在open时产生。起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针filp。

文件描述符的操作(如: open)返回的是一个文件描述符,内核会在每个进程空间中维护一个文件描述符表, 所有打开的文件都将通过此表中的文件描述符来引用;

而流(如: fopen)返回的是一个FILE结构指针, FILE结构是包含有文件描述符的,FILE结构函数可以看作是对fd直接操作的系统调用的封装, 它的优点是带有I/O缓存
每个进程在PCB(Process Control Block)即进程控制块中都保存着一份文件描述符表,文件描述符就是这个表的索引,文件描述表中每个表项都有一个指向已打开文件的指针,现在我们明确一下:已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。

##内存分配

内存分配方式有三种:

  1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
  2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  3. 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

##如何定位内存泄露

内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的、大小任意的(内存块的大小可以在程序运行期决定)、使用完后必须显示释放的内存。应用程序一般使用malloc、realloc、new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块。否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

C++程序缺乏相应的手段来检测内存信息,只能使用top指令观察进程的动态内存总额。而且程序退出时,我们无法获知任何内存泄漏信息

使用Linux命令回收内存,可以使用ps、kill两个命令检测内存使用情况和进行回收。在使用超级用户权限时使用命令“ps”,它会列出所有正在运行的程序名称和对应的进程号(PID)。kill命令的工作原理是向Linux操作系统的内核送出一个系统操作信号和程序的进程号(PID)

##linux 的内存管理机制是什么?

Linux 虚拟内存的实现需要 6 种机制的支持:地址映射机制、内存分配回收机制、缓存和刷新机制、请求页机制、交换机制和内存共享机制内存管理程序通过映射机制把用户程序的逻辑地址映射到物理地址。当用户程序运行时,如果发现程序中要用的虚地址没有对应的物理内存,就发出了请求页要求。如果有空闲的内存可供分配,就请求分配内存(于是用到了内存的分配和回收),并把正在使用的物理页记录在缓存中(使用了缓存机制)。如果没有足够的内存可供分配,那么就调用交换机制;腾出一部分内存。另外,在地址映射中要通过 TLB(翻译后援存储器)来寻找物理页;交换机制中也要用到交换缓存,并且把物理页内容交换到交换文件中,也要修改页表来映射文件地址。

1…101112…18
You Wangqiu

You Wangqiu

世之奇伟、瑰怪,非常之观,常在于险远,而人之所罕至焉,故非有志者不能至也

171 日志
21 分类
24 标签
GitHub 知乎 E-Mail
© 2018 You Wangqiu
由 Hexo 强力驱动
主题 - NexT.Muse