笔者的某个目录下面有两千个c文件需要处理,为了快速实现,写了下面的代码去打开:
1 #include <stdio.h>
2 #include <string.h>
3
4 int main(void)
5 {
6 int i = 0;
7 FILE * fp = NULL;
8 char filename[10] = {0,};
9
10 printf("Hello!\n");
11
12 for(i=0;i<2000;i++) {
13 memset(filename, 10, 0);
14 sprintf(filename, "t%d.c", i);
15 fp = fopen(filename, "a+");
16 printf("Open %s successfully: Turn %d done!\n", filename, i);
17 }
18
19 return 0;
20 }
执行得很好,可是在上面15/16行之间加上一句写的话,却执行失败了:
…...........
15 fp = fopen(filename, "a+");
16 fwrite(filename, 1, strlen(filename), fp);
17 printf("Open %s successfully: Turn %d done!\n", filename, i);
…..........
执行结果如下:
Open t1018.c successfully: Turn 1018 done!
Open t1019.c successfully: Turn 1019 done!
Open t1020.c successfully: Turn 1020 done!
Segmentation fault (core dumped)。
正在百思不得其解的时候,突然想起了大三操作系统课程上讲进程块的时候提到了打开文件记录表这个信息,于是打开0.96内核的源代码,进去瞅了下,果然记录每个进程打开文件的数目是一个数组,而不是可以无限扩展的链表,0.96/linux/include/linux/sched.h中的代码如下:
112 struct task_struct {
113 /* these are hardcoded - don't touch */
114 long state; /* -1 unrunnable, 0 runnable, >0 stopped */
115 long counter;
116 long priority;
117 long signal;
118 struct sigaction sigaction[32];
119 long blocked; /* bitmap of masked signals */
120 /* various fields */
121 int exit_code;
122 int dumpable;
123 unsigned long start_code,end_code,end_data,brk,start_stack;
124 long pid,pgrp,session,leader;
125 int groups[NGROUPS];
126 /*
127 * pointers to (original) parent process, youngest child, younger sibling,
128 * older sibling, respectively. (p->father can be replaced with
129 * p->p_pptr->pid)
130 */
131 struct task_struct *p_opptr,*p_pptr, *p_cptr, *p_ysptr, *p_osptr;
132 /*
133 * sleep makes a singly linked list with this.
134 */
135 struct task_struct *next_wait;
136 unsigned short uid,euid,suid;
137 unsigned short gid,egid,sgid;
138 unsigned long timeout;
…...............
156 struct {
157 struct inode * library;
158 unsigned long start;
159 unsigned long length;
160 } libraries[MAX_SHARED_LIBS];
161 int numlibraries;
162 struct file * filp[NR_OPEN];
163 unsigned long close_on_exec;
164 /* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
165 struct desc_struct ldt[3];
166 /* tss for this task */
167 struct tss_struct tss;
168 };
在0.96内核里面0.96/linux/include/linux/fs.h中,NR_OPEN被定义成一个比较小的数目:
#define NR_OPEN 32
虽然我用的是4.2的内核,但这个限制仍然存在。具体可参考文件uapi/linux/limits.h,里面定义了打开文件数目、文件名长度等限制。在proc/fs等模块中,会包含这个头文件。
#define NR_OPEN 1024
#define NGROUPS_MAX 65536 /* supplemental group IDs are available */
#define ARG_MAX 131072 /* # bytes of args + environ for exec() */
#define LINK_MAX 127 /* # links a file may have */
#define MAX_CANON 255 /* size of the canonical input queue */
#define MAX_INPUT 255 /* size of the type-ahead buffer */
#define NAME_MAX 255 /* # chars in a file name */
#define PATH_MAX 4096 /* # chars in a path name including nul */
#define PIPE_BUF 4096 /* # bytes in atomic write to a pipe */
#define XATTR_NAME_MAX 255 /* # chars in an extended attribute name */
#define XATTR_SIZE_MAX 65536 /* size of an extended attribute value (64k) */
#define XATTR_LIST_MAX 65536 /* size of extended attribute namelist (64k) */
#define RTSIG_MAX 32
但接着问题来了,为啥只是调用fopen()没有问题,在它后面调用fwrite()之后才会出现问题呢?而且,为何t1020.c以及之前的写操作都没有落盘呢?后来又仔细想了下vfs/文件系统/bio/块设备这些模块的具体流程,直觉是vfs和文件系统之间有延迟分配:
1.文件描述符只有真正写的时候才会分配,这样才会占用进程描述块中打开文件表中的一个槽位;
2.现代的大部分文件系统数据都是先写到为文件系统分配的page cache里面,只有被要求刷新之后,
才会从磁盘上去寻找一块接纳page buffer中数据的空间,然后把脏page cache的内容写回。而在我上面的程序执行失败之前,一直没有调用fflush()去刷新,数据自然不能落盘。后来在fwrite()之后,加上了一句fflush(fp),果然之前写出的数据都能落盘。
通过上面的简单程序,可以看到如果要对C语言的IO操作有深入认识,有赖于对内核中文件系统、系统IO路径的深入理解,只有这样我们才能透过段错误、数据无法写入等现象,看到背后文件系统在执行的本质。
相关连接:
http://blog.csdn.net/kai_ding/article/details/9914629
http://blog.csdn.net/dongpy/article/details/4552062