关于ulimit的深入思考

在我总结ulimit之前,让我们回顾一下,为什么要修改ulimit的值?


QUOTE:
2.1. 如何管理多个连接?
“我想同时监控一个以上的文件描述符(fd)/连接(connection)/流(stream),应该怎么办?”

使用 select() 或 poll() 函数。

注意:select() 在BSD中被引入,而poll()是SysV STREAM流控制的产物。因此,这里就有了平台移植上的考虑:纯粹的BSD系统可能仍然缺少poll(),而早一些的SVR3系统中可能没有select(),尽管在SVR4中将其加入。目前两者都是POSIX. 1g标准,(译者注:因此在Linux上两者都存在)

select()和poll()本质上来讲做的是同一件事,只是完成的方法不一样。两者都通过检验一组文件描述符来检测是否有特定的时间将在上面发生并在一定的时间内等待其发生。

[重要事项:无论select()还是poll()都不对普通文件起很大效用,它们着重用于套接口(socket)、管道(pipe)、伪终端(pty)、终端设备(tty)和其他一些字符设备,但是这些操作都是系统相关(system-dependent)的。]

2.1.1. 我如何使用select()函数?
select()函数的接口主要是建立在一种叫'fd_set'类型的基础上。它('fd_set') 是一组文件描述符(fd)的集合。由于fd_set类型的长度在不同平台上不同,因此应该用一组标准的宏定义来处理此类变量:

  fd_set set;
  FD_ZERO(&set);     /* 将set清零 */
  FD_SET(fd, &set);   /* 将fd加入set */
  FD_CLR(fd, &set);   /* 将fd从set中清除 */
  FD_ISSET(fd, &set); /* 如果fd在set中则真 */
   
在过去,一个fd_set通常只能包含少于等于32个文件描述符,因为fd_set其实只用了一个int的比特矢量来实现,在大多数情况下,检查fd_set能包括任意值的文件描述符是系统的责任,但确定你的fd_set到底能放多少有时你应该检查/修改宏FD_SETSIZE的值。*这个值是系统相关的*,同时检查你的系统中的select() 的man手册。有一些系统对多于1024个文件描述符的支持有问题。[译者注: Linux就是这样的系统!你会发现sizeof(fd_set)的结果是128(*8 = FD_SETSIZE=1024) 尽管很少你会遇到这种情况。]

select的基本接口十分简单:

  int select(int nfds, fd_set *readset, fd_set *writeset,
          fd_set *exceptset, struct timeval *timeout);
   
其中:

nfds  
  需要检查的文件描述符个数,数值应该比是三组fd_set中最大数
  更大,而不是实际文件描述符的总数。
readset  
  用来检查可读性的一组文件描述符。
writeset
  用来检查可写性的一组文件描述符。
exceptset
  用来检查意外状态的文件描述符。(注:错误并不是意外状态)
timeout
  NULL指针代表无限等待,否则是指向timeval结构的指针,代表最
  长等待时间。(如果其中tv_sec和tv_usec都等于0, 则文件描述符
  的状态不被影响,但函数并不挂起)
   
函数将返回响应操作的对应操作文件描述符的总数,且三组数据均在恰当位置被修改,只有响应操作的那一些没有修改。接着应该用FD_ISSET宏来查找返回的文件描述符组。

这里是一个简单的测试单个文件描述符可读性的例子:

  int isready(int fd)
  {
      int rc;
      fd_set fds;
      struct timeval tv;
 
      FD_ZERO(&fds);
      FD_SET(fd,&fds);
      tv.tv_sec = tv.tv_usec = 0;
 
    rc = select(fd+1, &fds, NULL, NULL, &tv);
      if (rc < 0)
      return -1;
 
      return FD_ISSET(fd,&fds) ? 1 : 0;
  }
   
当然如果我们把NULL指针作为fd_set传入的话,这就表示我们对这种操作的发生不感兴趣,但select() 还是会等待直到其发生或者超过等待时间。

[译者注:在Linux中,timeout指的是程序在非sleep状态中度过的时间,而不是实际上过去的时间,这就会引起和非Linux平台移植上的时间不等问题。移植问题还包括在System V风格中select()在函数退出前会把timeout设为未定义的 NULL状态,而在BSD中则不是这样,Linux在这点上遵从System V,因此在重复利用timeout指针问题上也应该注意。]

2.1.2. 我如何使用poll()?
poll()接受一个指向结构'struct pollfd'列表的指针,其中包括了你想测试的文件描述符和事件。事件由一个在结构中事件域的比特掩码确定。当前的结构在调用后将被填写并在事件发生后返回。在SVR4(可能更早的一些版本)中的 "poll.h"文件中包含了用于确定事件的一些宏定义。事件的等待时间精确到毫秒 (但令人困惑的是等待时间的类型却是int),当等待时间为0时,poll()函数立即返回,-1则使poll()一直挂起直到一个指定事件发生。下面是pollfd的结构。

  struct pollfd {
      int fd;     /* 文件描述符 */
      short events; /* 等待的事件 */
      short revents; /* 实际发生了的事件 */
  };
   
于select()十分相似,当返回正值时,代表满足响应事件的文件描述符的个数,如果返回0则代表在规定事件内没有事件发生。如发现返回为负则应该立即查看 errno,因为这代表有错误发生。

如果没有事件发生,revents会被清空,所以你不必多此一举。

这里是一个例子

  /* 检测两个文件描述符,分别为一般数据和高优先数据。如果事件发生
    则用相关描述符和优先度调用函数handler(),无时间限制等待,直到
    错误发生或描述符挂起。*/
 
  #include <stdlib.h>
  #include <stdio.h>

  #include <sys/types.h>
  #include <stropts.h>
  #include <poll.h>

  #include <unistd.h>
  #include <errno.h>
  #include <string.h>

  #define NORMAL_DATA 1
  #define HIPRI_DATA 2

  int poll_two_normal(int fd1,int fd2)
  {
    struct pollfd poll_list[2];
    int retval;

    poll_list[0].fd = fd1;
    poll_list[1].fd = fd2;
    poll_list[0].events = POLLIN|POLLPRI;
    poll_list[1].events = POLLIN|POLLPRI;

    while(1)
    {
      retval = poll(poll_list,(unsigned long)2,-1);
      /* retval 总是大于0或为-1,因为我们在阻塞中工作 */

      if(retval < 0)
      {
          fprintf(stderr,"poll错误: %s ",strerror(errno));
          return -1;
      }
 
      if(((poll_list[0].revents&POLLHUP) == POLLHUP) ||
        ((poll_list[0].revents&POLLERR) == POLLERR) ||
        ((poll_list[0].revents&POLLNVAL) == POLLNVAL) ||
        ((poll_list[1].revents&POLLHUP) == POLLHUP) ||
        ((poll_list[1].revents&POLLERR) == POLLERR) ||
        ((poll_list[1].revents&POLLNVAL) == POLLNVAL))
        return 0;

      if((poll_list[0].revents&POLLIN) == POLLIN)
        handle(poll_list[0].fd,NORMAL_DATA);
      if((poll_list[0].revents&POLLPRI) == POLLPRI)
        handle(poll_list[0].fd,HIPRI_DATA);
      if((poll_list[1].revents&POLLIN) == POLLIN)
        handle(poll_list[1].fd,NORMAL_DATA);
      if((poll_list[1].revents&POLLPRI) == POLLPRI)
        handle(poll_list[1].fd,HIPRI_DATA);
    }
  }
   
2.1.3. 我是否可以同时使用SysV IPC和select()/poll()?
*不能。* (除非在AIX上,因为它用一个无比奇怪的方法来实现这种组合)

一般来说,同时使用select()或poll()和SysV 消息队列会带来许多麻烦。SysV IPC的对象并不是用文件描述符来处理的,所以它们不能被传递给select()和 poll()。这里有几种解决方法,其粗暴程度各不相同:


完全放弃使用SysV IPC。 :-)

用fork(),然后让子进程来处理SysV IPC,然后用管道或套接口和父进程 说话。父进程则使用select()。

同上,但让子进程用select(),然后和父亲用消息队列交流。

安排进程发送消息给你,在发送消息后再发送一个信号。*警告*:要做好 这个并不简单,非常容易写出会丢失消息或引起死锁的程序。

另外还有其他方法。
比较彻底的方法是使用epoll


QUOTE:
Linux2.6内核epoll介绍(0.1版本)- -
                         


[作者]:滕昱,2005/3/30,0.1版本

[版权声明]:此文档遵循GNU自由文档许可证(GNU Free Documentation License).任何人可以自由复制,分发,修改,不过如果方便,请注明出处和作者:)

(1)导言:

首先,我强烈建议大家阅读Richard Stevens著作《TCP/IP Illustracted Volume 1,2,3》和《UNIX Network Programming Volume 1,2》。虽然他离开我们大家已经5年多了,但是他的书依然是进入网络编程的最直接的道路。其中的3卷的《TCP/IP Illustracted》卷1是必读-如果你不了解tcp协议各个选项的详细定义,你就失去了优化程序重要的一个手段。卷2,3可以选读一下。比如卷2讲解的是4.4BSD内核TCP/IP协议栈实现----这个版本的协议栈几乎影响了现在所有的主流os,但是因为年代久远,内容不一定那么vogue.在这里我多推荐一本《The Linux Networking Architecture--Design and Implementation of Network Protocols in the Linux Kernel》,以2.4内核讲解Linux TCP/IP实现,相当不错.作为一个现实世界中的实现,很多时候你必须作很多权衡,这时候参考一个久经考验的系统更有实际意义。举个例子,linux内核中sk_buff结构为了追求速度和安全,牺牲了部分内存,所以在发送TCP包的时候,无论应用层数据多大,sk_buff最小也有272的字节.

其实对于socket应用层程序来说,《UNIX Network Programming Volume 1》意义更大一点.2003年的时候,这本书出了最新的第3版本,不过主要还是修订第2版本。其中第6章《I/O Multiplexing》是最重要的。Stevens给出了网络IO的基本模型。在这里最重要的莫过于select模型和Asynchronous I/O模型.从理论上说,AIO似乎是最高效的,你的IO操作可以立即返回,然后等待os告诉你IO操作完成。但是一直以来,如何实现就没有一个完美的方案。最著名的windows完成端口实现的AIO,实际上也是内部用线程池实现的罢了,最后的结果是IO有个线程池,你应用也需要一个线程池......很多文档其实已经指出了这带来的线程context-switch带来的代价。

在linux平台上,关于网络AIO一直是改动最多的地方,2.4的年代就有很多AIO内核patch,最著名的应该算是SGI那个。但是一直到2.6内核发布,网络模块的AIO一直没有进入稳定内核版本(大部分都是使用用户线程模拟方法,在使用了NPTL的linux上面其实和windows的完成端口基本上差不多了)。2.6内核所支持的AIO特指磁盘的AIO---支持io_submit(),io_getevents()以及对Direct IO的支持(就是绕过VFS系统buffer直接写硬盘,对于流服务器在内存平稳性上有相当帮助)。

所以,剩下的select模型基本上就是我们在linux上面的唯一选择,其实,如果加上no-block socket的配置,可以完成一个"伪"AIO的实现,只不过推动力在于你而不是os而已。不过传统的select/poll函数有着一些无法忍受的缺点,所以改进一直是2.4-2.5开发版本内核的任务,包括/dev/poll,realtime signal等等。最终,Davide Libenzi开发的epoll进入2.6内核成为正式的解决方案

(2)epoll的优点

<1>支持一个进程打开大数目的socket描述符(FD)

select最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了 。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

<2>IO效率不随FD数目增加而线性下降

传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。

<3>使用mmap加速内核与用户空间的消息传递。

这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工mmap这一步的。

<4>内核微调

这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小---通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。

(3)epoll的使用

令人高兴的是,2.6内核的epoll比其2.5开发版本的/dev/epoll简洁了许多,所以,大部分情况下,强大的东西往往是简单的。唯一有点麻烦是epoll有2种工作方式:LT和ET。

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.

ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。

epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用,具体用法请参考http://www.xmailserver.org/linux-patches/nio-improve.html
http://www.kegel.com/rn/也有一个完整的例子,大家一看就知道如何使用了

(4)Leader/follower模式线程pool实现,以及和epoll的配合

.....未完成,主要是要避免过多的epoll_ctl调用,以及尝试使用EPOLLONESHOT加速......

(5)benchmark

.......未完成
修改内核来提高FD_SETSIZE的方法,网上到处都有,过程就不多说了,主要起作用的文件是:
  1)修改/usr/src/linux/include/linux/posix_types.h
    设置fd_set支持的最大数量
    #define __FD_SETSIZE 1024 --->; 65536  
 
  2)修改/usr/src/linux/include/linux/fs.h
    设置最大打开文件数量(TCP连接数量)
    #define INR_OPEN 1024 --->; 65536
    #define NR_FILE 8192 --->; 65536
    #define NR_RESERVED_FILES 10 --->; 128
 
  3)修改/usr/src/linux/include/net/tcp.h
    设置TIMEOUT的时间为1秒
    #define TCP_TIMEWAIT_LEN (60*HZ) 60 --->; 1*HZ
    设置在backlog队列里的半连接的重试次数,每次都会花相应的时间,本质上 也是减少重试时间
    #define TCP_SYNACK_RETRIES 5 --->; 3

修改后重新编译内核。

接下去是最重要的部分,在网上总有这样的问题:


QUOTE:
不知道为什么
我编译后
还是不行
ulimit显示还是1024
是不是错了?
能否告知具体做法?我是菜鸟,不好意思!谢谢了!

 

QUOTE:
本人经过编译内核,open files 已经解决(ulimit -a显示),但在程序使用中提示open files 还是1024,请问个位高手,这是何故?我修改了 linux/posix_types.h中得__FD_SETSIZE
linux/fs.h中得INR_OPEN
那么你就有必要看这篇文章,让我详细的说明一下:
1。能够影响ulimit的参数会有这几个文件,除了/etc/profile,/etc/init.d/functions,/etc/security/limits.conf几个系统默认文件外,以及任何写入的ulimit命令的脚本,如用户目录下的.bash_profile文件。
2。ulimit是随着进程的启动而生效的,它只能影响当前父进程以及由它衍生出来的子进程,一旦父进程结束,ulimit的值就将使用系统默认值。
3。进程启动所获得的ulimit值与用户登录进去用ulimit -a所查看的值是两个概念。这一点可能不太明白,因为linux启动的时候会启动init这个程序,调用
inittab->rc.sysinit->rcx.d->rc.local->minigetty开始侦听,因此进程启动时的ulimit只是在rcx.d下的启动脚本中设置,并在进程中继承,而用户登录系统时,会去读取/etc/profile配置文件,因而无关,这就是为什么那么多人迷惑的地方。
那么如何判断程序到底是得到了多少文件句柄呢?我们可以用GetMaxHandles这个函数而获得:GetMaxHandles,现在你可以在程序中写入一段代码把数字打印出来就明白了。


 

<think>好的,我现在需要帮助用户解决在Ubuntu 22.04系统中查看和修改交换空间以及内存限制的问题。首先,我应该回忆一下自己掌握的相关知识,并结合用户提供的引用内容来组织回答。 用户的问题分为两部分:查看和修改交换空间,以及内存限制。我需要分别处理这两个部分,确保每个步骤都清晰易懂。 首先,查看交换空间。我记得可以使用swapon命令或者free命令。在引用[1]和[2]中提到了交换文件的使用和大小设置,这可能与用户的需求相关。因此,我应该列出查看交换空间的方法,比如使用`swapon --show`和`free -h`,并解释输出结果的含义。 接下来是修改交换空间。根据引用[1]中的步骤,用户需要创建交换文件,使用dd命令,然后格式化和启用交换空间。不过,用户可能需要调整现有的交换空间,比如增加或减少。这里需要详细说明如何安全地调整交换空间,包括关闭现有的交换空间,修改交换文件的大小,然后重新启用。同时,引用[2]提到不同内存情况下交换空间的推荐大小,这一点也需要在修改建议中提到,帮助用户决定合适的大小。 然后是内存限制的问题。这部分可能涉及到系统内存的管理和设置。用户提到的“内存限制”可能是指进程的内存使用限制,或者系统的虚拟内存设置。我需要考虑使用ulimit命令来设置用户级别的内存限制,或者通过sysctl调整内核参数,比如vm.overcommit_memory。引用[5]提到了SSH的配置,但可能和内存限制无关,不过其他引用中没有直接相关的内容,需要依靠自己的知识。 在回答过程中,还需要注意引用相关的资料。比如在创建交换文件时引用[1],在推荐交换空间大小时引用[2]。同时,确保命令的正确性,比如使用正确的dd命令参数,避免数据丢失。 另外,用户可能不熟悉终端操作,所以步骤要详细,包括每一步的命令和解释。比如在修改交换空间时,需要先关闭交换空间,调整文件大小,再重新启用,并更新fstab文件以确保重启后生效。 关于内存限制的设置,可能需要区分临时和永久设置。使用ulimit是临时的,而修改/etc/security/limits.conf或sysctl.conf是永久的。这部分需要明确说明,避免用户配置错误。 最后,生成相关问题,帮助用户深入理解。比如如何优化交换空间性能,调整内存限制的应用场景,或者如何监控交换空间的使用情况。这些问题应该基于回答的内容,引导用户进一步学习。 在整理回答时,要确保结构清晰,分点说明,使用代码块标注命令,符合用户提供的格式要求。同时,检查LaTeX语法是否正确,虽然这个问题中可能用到的数学公式不多,但如果有的话要正确使用$符号。 现在,我需要将以上思考整理成一个连贯的回答,确保涵盖查看、修改交换空间和内存限制的所有步骤,引用正确的参考文献,并生成相关问题。</think>### Ubuntu 22.04 查看与修改交换空间及内存限制 --- #### 一、查看交换空间 1. **当前交换空间状态** 使用以下命令查看交换空间信息: ```bash swapon --show # 显示已启用的交换分区/文件 free -h # 显示内存和交换空间使用情况(包含总量、已用、空闲) ``` 输出示例: ``` NAME TYPE SIZE USED PRIO /swapfile file 2G 0B -2 ``` 2. **查看交换文件配置** 检查 `/etc/fstab` 文件确认交换空间是否开机自动挂载: ```bash cat /etc/fstab | grep swap ``` --- #### 二、修改交换空间 1. **调整交换文件大小** **步骤:** - **关闭现有交换空间**: ```bash sudo swapoff /swapfile ``` - **调整交换文件大小**(例如调整为4GB): ```bash sudo dd if=/dev/zero of=/swapfile bs=1M count=4096 # 创建4GB文件[^1] sudo chmod 600 /swapfile # 设置权限 sudo mkswap /swapfile # 格式化为交换文件 sudo swapon /swapfile # 启用新交换空间 ``` - **更新`/etc/fstab`**: 确保文件中包含以下行: ``` /swapfile swap swap defaults 0 0 ``` 2. **推荐交换空间大小** - 物理内存 ≤ 2GB:交换空间为内存的2倍 - 物理内存 > 2GB:交换空间建议为内存的1.5-2倍(大量音视频处理需≥3GB)[^2] --- #### 三、内存限制设置 1. **进程内存限制** - **临时限制**(例如限制单个进程内存为500MB): ```bash ulimit -m 524288 # 单位KB(500MB=500*1024=512000KB) ``` - **永久限制**: 编辑 `/etc/security/limits.conf`,添加: ``` * hard rss 524288 # 用户级内存限制 ``` 2. **系统级内存管理** - **调整虚拟内存策略**(通过`sysctl`): ```bash sudo sysctl vm.overcommit_memory=2 # 严格模式(禁止超量分配) ``` - **永久生效**: 在`/etc/sysctl.conf`中添加: ``` vm.overcommit_memory=2 ``` --- #### 四、验证修改 - **检查交换空间**: ```bash free -h # 确认新交换空间生效 swapon --show ``` - **检查内存限制**: ```bash ulimit -a | grep memory # 查看当前用户限制 ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值