由打开文件失败引发的思考

简介:

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
















本文转自存储之厨51CTO博客,原文链接:http://blog.51cto.com/xiamachao/1867239 ,如需转载请自行联系原作者




相关文章
linux实用技巧:cp时自动将软链接所指定的文件实体也一起copy(软链接将会变成目标文件实体)
linux实用技巧:cp时自动将软链接所指定的文件实体也一起copy(软链接将会变成目标文件实体)
linux实用技巧:cp时自动将软链接所指定的文件实体也一起copy(软链接将会变成目标文件实体)
|
Rust 安全 Java
编程语言新宠:Rust语言的特性、优势与实战入门
【10月更文挑战第27天】Rust语言以其独特的特性和优势在编程领域迅速崛起。本文介绍Rust的核心特性,如所有权系统和强大的并发处理能力,以及其性能和安全性优势。通过实战示例,如“Hello, World!”和线程编程,帮助读者快速入门Rust。
1151 1
|
存储 Python
Python装饰器2-__call__方法与类装饰器
__call__方法、创建类装饰器、装饰器的应用场景
Python装饰器2-__call__方法与类装饰器
|
存储 人工智能 安全
CPFS深度解析:并行文件存储加速AI创新
在生成式AI的大潮中,并行文件系统作为高性能数据底座,为AI算力提供高吞吐、低延迟的数据存储服务。在本话题中,我们将介绍阿里云并行文件存储CPFS针对AI智算场景而提供的产品能力演进与更新,深入讲解在性能、成本、稳定、安全等方面的技术创新。
1037 0
|
存储 自然语言处理 算法
【算法精讲系列】MGTE系列模型,RAG实施中的重要模型
检索增强生成(RAG)结合检索与生成技术,利用外部知识库提升大模型的回答准确性与丰富性。RAG的关键组件包括文本表示模型和排序模型,前者计算文本向量表示,后者进行精细排序。阿里巴巴通义实验室推出的GTE-Multilingual系列模型,具备高性能、长文档支持、多语言处理及弹性向量表示等特性,显著提升了RAG系统的检索与排序效果。该系列模型已在多个数据集上展示出优越性能,并支持多语言和长文本处理,适用于各种复杂应用场景。
2567 18
|
PyTorch 算法框架/工具 Python
PyTorch中的forward的理解
PyTorch中的forward的理解
511 0
|
存储 测试技术 数据库
数据驱动测试中的参数化
数据驱动测试中的参数化
151 1
|
网络协议 Docker 容器
Docker的4种网络模式
我们在使用docker run创建Docker容器时,可以用--net选项指定容器的网络模式,Docker有以下4种网络模式: · host模式,使用--net=host指定。
8985 0
|
机器学习/深度学习 算法 异构计算
还不理解GPU推理卡和训练卡(简单易懂)
还不理解GPU推理卡和训练卡(简单易懂)
16726 2
|
Rust 安全 JavaScript
你的下一个项目应该使用 Rust 编程的 7 个理由
你的下一个项目应该使用 Rust 编程的 7 个理由
2669 0