在 UNIX/Linux 系统中,一切皆文件,这句话想必都有听过。对于文件的操作几乎适用于所有的设备,这也就看出了文件操作的重要性了。在C语言再学习部分有讲过标准I/O文件操作,参看:C语言再学习 -- 文件 下面我们来讲解下系统文件I/O的。
一、文件描述符
1、文件描述符简介
首先从文件描述符开始讲起。因为,对于内核而言,所有打开的文件都是通过文件描述符引用的。那么文件描述符到底是什么?
文件描述符(file descriptor)通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,使用 open 或 create 返回的文件描述符标识该文件,将其作为参数传送给 read 或 write。
2、标准输入、标准输出和标准错误
按照惯例,UNIX 系统 shell 把文件描述符 0 与进程的标准输入(standard input)关联,文件描述符 1 与标准输出(standard output)关联,文件描述符 2 与标准错误(standard error)关联。这是各种 shell 以及很多应用程序使用的惯例,与 UNIX 内核无关。尽管如此,如果不遵循这种惯例,很多 UNIX 系统应用程序就不能正常工作。
这部分讲 shell 编程重定向时,可参看UNIX再学习 -- shell编程
POSIX 定义了 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 来代替 0、1、2。这三个符号常量的定义位于头文件 <unistd.h>
查看 /usr/include/unistd.h文件描述符的有效范围是 0 到 OPEN_MAX。一般来说,每个进程最多可以打开 64 个文件(0 — 63)。对于 FreeBSD 8.0、Linux 3.2.0、Mac OS X 10.6.8 以及 Solaris 10 来说,文件描述符的变化范围几乎是无限的,它只受系统配置的存储器总量、整型的字长以及系统管理员所配置的软限制和硬限制的约束。
/* Standard file descriptors. */
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */
OPEN_MAX:每个进程最大打开文件数。查看如下:
查看 man sysconfPEN_MAX - _SC_OPEN_MAX The maximum number of files that a process can have open at any time. Must not be less than _POSIX_OPEN_MAX (20).或参看:man sysconf()
#include <unistd.h>#include <stdio.h>int main (void){ printf("_SC_ARG_MAX: %ld\n", sysconf(_SC_ARG_MAX)); printf("_SC_CHILD_MAX: %ld\n", sysconf(_SC_CHILD_MAX)); printf("_SC_CLK_TCK: %ld\n", sysconf(_SC_CLK_TCK)); printf("_SC_NGROUPS_MAX: %ld\n", sysconf(_SC_NGROUPS_MAX)); printf("_SC_OPEN_MAX: %ld\n", sysconf(_SC_OPEN_MAX)); printf("_SC_JOB_CONTROL: %ld\n", sysconf(_SC_JOB_CONTROL)); printf("_SC_SAVED_IDS: %ld\n", sysconf(_SC_SAVED_IDS)); printf("_SC_VERSION: %ld\n", sysconf(_SC_VERSION));return 0;}输出结果:_SC_ARG_MAX: 2097152_SC_CHILD_MAX: 7892_SC_CLK_TCK: 100_SC_NGROUPS_MAX: 65536_SC_OPEN_MAX: 1024_SC_JOB_CONTROL: 1_SC_SAVED_IDS: 1_SC_VERSION: 200809可得,在 Ubuntu 12.04 下测试结果为:_SC_OPEN_MAX =1024
而在 /apue.3e/exercises/openmax.c 也是有获取系统的 _SC_OPEN_MAX 的程序,可自行查看。
3、最大文件描述符数
通过上面可知,默认的 Linux 进程的最大打开文件描述符数是 1024。但是在某些情况下这点文件描述符数是远远不够的,可根据需要进行更改。
第一种方法:通过 ulimit 命令修改
参看:ulimit 命令 ulimit 用于限制 shell 启动进程所占用的资源,支持以下各种类型的限制:所创建的内核文件的大小、进程数据块的大小、Shell 进程创建文件的大小、内存锁住的大小、常驻内存集的大小、打开文件描述符的数量、分配堆栈的最大大小、CPU 时间、单个用户的最大线程数、Shell 进程所能使用的最大虚拟内存。同时,它支持硬资源和软资源的限制。 作为临时限制,ulimit 可以作用于通过使用其命令登录的 shell 会话,在会话终止时便结束限制,并不影响于其他 shell 会话。而对于长期的固定限制,ulimit 命令语句又可以被添加到由登录 shell 读取的文件中,作用于特定的 shell 用户。 选项:选项 [options]含义例子-H设置硬资源限制,一旦设置不能增加。 ulimit – Hs 64;限制硬资源,线程栈大小为 64K。-S设置软资源限制,设置后可以增加,但是不能超过硬资源设置。 ulimit – Sn 32;限制软资源,32 个文件描述符。-a显示当前所有的 limit 信息。 ulimit – a;显示当前所有的 limit 信息。-c最大的 core 文件的大小, 以 blocks 为单位。 ulimit – c unlimited; 对生成的 core 文件的大小不进行限制。-d进程最大的数据段的大小,以 Kbytes 为单位。 ulimit -d unlimited;对进程的数据段大小不进行限制。-f进程可以创建文件的最大值,以 blocks 为单位。 ulimit – f 2048;限制进程可以创建的最大文件大小为 2048 blocks。-l最大可加锁内存大小,以 Kbytes 为单位。 ulimit – l 32;限制最大可加锁内存大小为 32 Kbytes。-m最大内存大小,以 Kbytes 为单位。 ulimit – m unlimited;对最大内存不进行限制。-n可以打开最大文件描述符的数量。 ulimit – n 128;限制最大可以使用 128 个文件描述符。-p管道缓冲区的大小,以 Kbytes 为单位。 ulimit – p 512;限制管道缓冲区的大小为 512 Kbytes。-s线程栈大小,以 Kbytes 为单位。 ulimit – s 512;限制线程栈的大小为 512 Kbytes。-t最大的 CPU 占用时间,以秒为单位。 ulimit – t unlimited;对最大的 CPU 占用时间不进行限制。-u用户最大可用的进程数。 ulimit – u 64;限制用户最多可以使用 64 个进程。-v进程最大可用的虚拟内存,以 Kbytes 为单位。 ulimit – v 200000;限制最大可用的虚拟内存为 200000 Kbytes。
英文信息,可 man bash 查看
ulimit [-HSTabcdefilmnpqrstuvx [limit]] Provides control over the resources available to the shell and to processes started by it, on systems that allow such con‐ trol. The -H and -S options specify that the hard or soft limit is set for the given resource. A hard limit cannot be increased by a non-root user once it is set; a soft limit may be increased up to the value of the hard limit. If neither -H nor -S is specified, both the soft and hard limits are set. The value of limit can be a number in the unit specified for the resource or one of the special values hard, soft, or unlimited, which stand for the current hard limit, the current soft limit, and no limit, respectively. If limit is omitted, the current value of the soft limit of the resource is printed, unless the -H option is given. When more than one resource is specified, the limit name and unit are printed before the value. Other options are interpreted as follows: -a All current limits are reported -b The maximum socket buffer size -c The maximum size of core files created -d The maximum size of a process's data segment -e The maximum scheduling priority ("nice") -f The maximum size of files written by the shell and its children -i The maximum number of pending signals -l The maximum size that may be locked into memory -m The maximum resident set size (many systems do not honor this limit) -n The maximum number of open file descriptors (most systems do not allow this value to be set) -p The pipe size in 512-byte blocks (this may not be set) -q The maximum number of bytes in POSIX message queues -r The maximum real-time scheduling priority -s The maximum stack size -t The maximum amount of cpu time in seconds -u The maximum number of processes available to a single user -v The maximum amount of virtual memory available to the shell and, on some systems, to its children -x The maximum number of file locks -T The maximum number of threads If limit is given, it is the new value of the specified resource (the -a option is display only). If no option is given, then -f is assumed. Values are in 1024-byte increments, except for -t, which is in seconds, -p, which is in units of 512-byte blocks, and -T, -b, -n, and -u, which are unscaled values. The return status is 0 unless an invalid option or argu‐ ment is supplied, or an error occurs while setting a new limit.实例:
显示当前最大打开文件描述符数
# ulimit -n1024
修改当前用户环境下的最大打开文件描述符数 (临时更改)
设置当前用户环境下的最大打开文件描述符数为 65536# ulimit -HSn 65536查看:# ulimit -n65536
将指令添加到脚本中 (永久更改)
添加到 单用户目录下:/etc/bash.bashrc 或 ~/.bashrc
echo "ulimit -HSn 65536" >> ~/.bashrc或者echo "ulimit -HSn 65536" >> /etc/bash.bashrc我使用的是 root 超级用户登录,而非 non-root 登录的,所以放在 /etc/profile 等针对所有用户的不起作用。
还有网上说的写到 rc.local 我也没有实现。
第二种方法:修改 limits.conf 文件 (永久更改)
在 /etc/security/limits.conf 文件最后加入如下两行:
* soft nofile 65536* hard nofile 65536其中 * 代表所有用户,nofile 是代表最大文件打开数。用 non-root 登录,通过 ulimit -n 查看是否生效。
告诉你个不幸的消息,很遗憾我的没有生效。将 * 改为 root 则可以生效,因为我用的不是 non-root 登录的。
查看最大文件描述符数上限
当然,这个最大文件描述符数也是有上限的,比如设置一个很大的数,会提示如下错误:# ulimit -n 100000000bash: ulimit: open files: 无法修改 limit 值: 不允许的操作而最大文件描述符数的上限值是在 /proc/sys/fs/nr_open 设置的,默认为 1048576 你也可以修改它,比如执行:
echo 2000000 > /proc/sys/fs/nr_open
4、混淆的概念
在我看到的很多文章里,有不少将 /proc/sys/fs/file-max 看作了 最大文件描述符数的上限值,其实是不对的。那么到底 file-max 和 nr_open 有什么区别呢? 查看 linux/Documentation/sysctl/fs.txt,可看到关于 file-max 和 nr_open 的解释:file-max & file-nr:The kernel allocates file handles dynamically, but as yet itdoesn't free them again.The value in file-max denotes the maximum number of file-handles that the Linux kernel will allocate. When you get lotsof error messages about running out of file handles, you mightwant to increase this limit.Historically, the three values in file-nr denoted the number ofallocated file handles, the number of allocated but unused filehandles, and the maximum number of file handles. Linux 2.6 alwaysreports 0 as the number of free file handles -- this is not anerror, it just means that the number of allocated file handlesexactly matches the number of used file handles.Attempts to allocate more file descriptors than file-max arereported with printk, look for "VFS: file-max limit <number>reached".
nr_open:This denotes the maximum number of file-handles a process canallocate. Default value is 1024*1024 (1048576) which should beenough for most machines. Actual limit depends on RLIMIT_NOFILEresource limit.翻译一下: 关于 file-max 内核可以动态的分配文件句柄,但到目前为止是不会释放它们的。
file-max 的值表示Linux内核分配的最大文件句柄数,如果你看到了很多关于打开文件数已经达到了最大值的错误信息,你可以试着增加该值的限制。 在kernel 2.6之前的版本中,file-nr 中的值由三部分组成,分别为:1.已经分配的文件句柄数,2.已经分配但没有使用的文件句柄数,3.最大文件句柄数。但在 kernel 2.6 版本中第二项的值总为 0,这并不是一个错误,它实际上意味着已经分配的文件句柄无一浪费的都已经被使用了。
关于 nr_open nr_open 的值表示一个进程可以分配的最大文件句柄数。 默认值为1024 * 1024(1048576),应该足够用于大多数机器。 实际限制取决于RLIMIT_NOFILE资源限制。
(1)概念解析
先说 file_max
前面的翻译,我们已经知道 file-max 的值表示Linux内核分配的最大文件句柄数。 让我们再查看一下更多关于它的内容: man proc/proc/sys/fs/file-max This file defines a system-wide limit on the number of open files for all processes. (See also setrlimit(2), which can be used by a process to set the per-process limit, RLIMIT_NOFILE, on the number of files it may open.) If you get lots of error messages about running out of file handles, try increasing this value: echo 100000 > /proc/sys/fs/file-max The kernel constant NR_OPEN imposes an upper limit on the value that may be placed in file-max. If you increase /proc/sys/fs/file-max, be sure to increase /proc/sys/fs/inode-max to 3-4 times the new value of /proc/sys/fs/file-max, or you will run out of inodes.翻译一下: 该文件定义了所有进程的打开文件数量的系统范围限制。 (另见setrlimit(2),可以是被一个进程用来设置每个进程的限制,RLIMIT_NOFILE,对它可能打开的文件数。)如果你收到很多错误关于运行文件句柄的消息,尝试增加此值。
内核常量 NR_OPEN 对可以放在 file-max中 的值施加上限。如果增加 /proc/sys/fs/file-max,请确保将 /pro /sys/fs/inode-max 增加到新值的 3-4 倍,否则 /proc/sys/fs/file-max,否则将用尽inode。 比 file-max 中的值大 3-4 倍,因为 stdin,stdout 和网络套接字也需要一个 inode 来处理它们。
意思是,修改file-max时也要按其值的 3-4 倍来修改 inode-max。 而file-max 的值我们可以使用 cat 查看:
# cat /proc/sys/fs/file-max100987而有种说法是 file-max 一般为内存大小(KB)的 10% 来计算,如果使用 shell,可以这样计算:
# grep MemTotal /proc/meminfo | awk '{printf("%d\n",$2/10)}'102479经比较,可能由于有各种其他原因导致 file-max 没有设置为内存的 10%。
再有关于 file-nr 中的值由三部分组成,分别为:1.已经分配的文件句柄数,2.已经分配但没有使用的文件句柄数,3.最大文件句柄数。第二项的值总为 0,这并不是一个错误,它实际上意味着已经分配的文件句柄无一浪费的都已经被使用了。
# cat /proc/sys/fs/file-nr64640100987
而,file-max 的值也有两种方式修改: 第一种:临时更改
echo 1000000 > /proc/sys/fs/file-max第二种:永久更改 修改 /etc/sysctl.conf 文件,末尾增加 fs.file-max = 1000000
注意几句话, file-max 的值表示Linux内核分配的最大文件句柄数。 file-max 该文件定义了所有进程的打开文件数量的系统范围限制。(系统级)
ulimit 用于限制 shell 启动进程所占用的资源。 (进程级)
nr_open 的值表示一个进程可以分配的最大文件句柄数。 (进程级) 这也间接说明了,nr_open 是 ulimit -HSn 的上限值。
(2)系统级 和 进程级 的区别
首先,系统级
我们刚才有查看 file-nr:# cat /proc/sys/fs/file-nr64640100987file-nr 中的值由三部分组成,分别为:1.已经分配的文件句柄数,2.已经分配但没有使用的文件句柄数,3.最大文件句柄数。也就是说,系统文件描述符的数量目前被使用 6464. 例如,查看 proc 目录:
使用 ps 指令查看当前终端启动的进程 使用 ps -aux 表示显示所有包含其他使用者的进程
使用 ps -aux | more 表示将ps -aux的结果进行分屏显示
# ps PID TTY TIME CMD 2401 pts/1 00:00:00 bash 3054 pts/1 00:00:00 ps
# ps -aux | moreWarning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.htmlUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 1 0.0 0.1 3612 2024 ? Ss 10:53 0:01 /sbin/initroot 2 0.0 0.0 0 0 ? S 10:53 0:00 [kthreadd]root 3 0.0 0.0 0 0 ? S 10:53 0:00 [ksoftirqd/0]root 6 0.0 0.0 0 0 ? S 10:53 0:00 [migration/0]root 7 0.0 0.0 0 0 ? S 10:53 0:01 [watchdog/0]root 8 0.0 0.0 0 0 ? S< 10:53 0:00 [cpuset]root 9 0.0 0.0 0 0 ? S< 10:53 0:00 [khelper]root 10 0.0 0.0 0 0 ? S 10:53 0:00 [kdevtmpfs].....那我们就看看当前终端启动的进程 2401 lsof 命令可以查出某个进程打开的文件数目,wc -l 只显示列数 lsof使用,参看:lsof 命令
# lsof | grep '2401' | wc -l20意思是有20个文件被打开查询进程使用的文件描述符数目
# ls -l /proc/2401/fd/ | wc -l5意思是文件描述符只有5个所以说,一个文件即使被打开,也可能没有文件描述符,比如当前工作目录,内存映射文件和可执行文本文件。
以上的 fd 就是系统文件描述符。
再者,进程级
比如,使用open 和 close 函数查看 fd 的值://open函数和close函数的使用#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(void){//while (1){int fd = open("a.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);if(-1 == fd){perror("open"),exit(-1);}printf ("fd = %d\n", fd);#if 1//2.关闭文件int res = close(fd);if(-1 == res){perror("close"),exit(-1);}#endif //}return 0;}输出结果:fd = 3该程序,除去标准输入(0)、标准输出(1)、标准错误(2),文件描述符从 3 开始。 将上面的程序改为循环,其结果为:
忽略....fd = 1019fd = 1020fd = 1021fd = 1022fd = 1023open: Too many open files可以看到当 fd = 1023 再往后,出现错误 open: Too many open files,而这个 1024,不就是在 Ubuntu 12.04 下测试结果为:_SC_OPEN_MAX =1024 即:
# ulimit -n1024这里的 fd 是进程级的文件描述符。
5、文件描述符和文件指针
(1)文件描述符和文件指针比较
参看:文件句柄(file handles) & 文件描述符(file descriptors)文件描述符(fd):在linux系统中打开文件就会获得文件描述符,它是个很小的正整数。每个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。 文件指针(FILE*):C 语言中使用文件指针做为 I/O的句柄。文件指针指向进程用户区中的一个被称为 FILE 结构的数据结构。FILE 结构包括一个缓冲区和一个文件描述符。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)。
通过 /usr/include/libio.h 查看 C 语言中 _IO_FILE 结构体的定义:
struct _IO_FILE { int _flags;/* High-order word is _IO_MAGIC; rest is flags. */#define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ char* _IO_read_ptr;/* Current read pointer */ char* _IO_read_end;/* End of get area. */ char* _IO_read_base;/* Start of putback+get area. */ char* _IO_write_base;/* Start of put area. */ char* _IO_write_ptr;/* Current put pointer. */ char* _IO_write_end;/* End of put area. */ char* _IO_buf_base;/* Start of reserve area. */ char* _IO_buf_end;/* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno;#if 0 int _blksize;#else int _flags2;#endif _IO_off_t _old_offset; /* This used to be _offset but it's too small. */#define __HAVE_COLUMN /* temporary */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; /* char* _save_gptr; char* _save_egptr; */ _IO_lock_t *_lock;#ifdef _IO_USE_OLD_IO_FILE};我们可以通过 /usr/include/stdio.h C 语言中 FILE 结构体的定义:
/* Define outside of namespace so the C++ is happy. */struct _IO_FILE;__BEGIN_NAMESPACE_STD/* The opaque type of streams. This is the definition used elsewhere. */typedef struct _IO_FILE FILE;__END_NAMESPACE_STD#if defined __USE_LARGEFILE64 || defined __USE_SVID || defined __USE_POSIX \ || defined __USE_BSD || defined __USE_ISOC99 || defined __USE_XOPEN \ || defined __USE_POSIX2__USING_NAMESPACE_STD(FILE)#endif# define __FILE_defined1#endif /* FILE not defined. */#undef__need_FILE#if !defined ____FILE_defined && defined __need___FILE/* The opaque type of streams. This is the definition used elsewhere. */typedef struct _IO_FILE __FILE;在这个_IO_FILE结构体中的“int _fileno”就是fd,即文件描述符。举例验证:
#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>int main(){ char buf[50] = {"file descriptor demo"}; FILE *myfile; myfile = fopen("test","w+"); if(!myfile){ printf("error: openfile failed!\n"); } printf("The openfile's descriptor is %d\n", myfile->_fileno); if(write(myfile->_fileno,buf,50)<0){ perror("error: write file failed!\n"); exit(1); }else{ printf("writefile successed!\n"); } exit(0);}输出结果:The openfile's descriptor is 3writefile successed!查看 testcat test file descriptor demoroot
(2)文件描述符和文件指针相互转换
文件指针转文件描述符:
参看:百度百科--fileno 函数 fileno int _fileno( FILE *stream );fileno 用来取得参数stream指定的文件流所使用的文件描述符。
实例:
#include <stdio.h>int main( void ){printf( "The file descriptor for stdin is %d\n", fileno( stdin ) );printf( "The file descriptor for stdout is %d\n", fileno( stdout ) );printf( "The file descriptor for stderr is %d\n", fileno( stderr ) );return 0;}输出结果:The file descriptor for stdin is 0The file descriptor for stdout is 1The file descriptor for stderr is 2
#include <stdio.h>int main(void){FILE *fp;int fd;fp = fopen("/etc/passwd", "r");fd = fileno(fp);printf("fd = %d\n", fd);fclose(fp);return 0;}输出结果:fd = 3
文件描述符转文件指针:
参看:百度百科--fdopen 函数 fdopenFILE* fdopen(int fd, const char* type);
fdopen 取一个现存的文件描述符,并使一个标准的I / O流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数获得的描述符。因为这些特殊类型的文件不能用标准I/O fopen函数打开,首先必须先调用设备专用函数以获得一个文件描述符,然后用fdopen使一个标准I/O流与该描述符相结合。
实例:标准输出文件描述符为1
#include<stdio.h>int main (void){FILE * fp = fdopen (1, "w+");fprintf (fp, "%s\n", "hello!");fclose (fp);return 0;}输出结果:hello!