笔者的某个目录下面有两千个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<8192;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