Linux文件系统3--打开文件
1.前言
本文所述关于文件管理的系列文章主要是对陈莉君老师所讲述的文件系统管理知识讲座的整理。
Linux可以支持不同的文件系统,它源于unix文件系统,也是unix文件系统的一大特色。
本文将以不同文件系统之间的拷贝为实例进行讲述
2. 实例:文件拷贝

图 不同文件系统之间的拷贝

图 文件拷贝对应的C语言片段
3.打开文件
3.1 open函数
文件读写之前都要先打开文件,打开函数的原型如下:

- open通过路径名、标志和mask信息,打开或创建文件,最后返回此文件对应的fd
- 用户态下调用open,进入系统调用处理程序后,会调用内核相应的系统调用服务例程
3.2 打开文件的内核实现
从整体流程来看,open的内核实现如下:
进程从用户态获取路径名到内核缓冲区;
然后查找到父目录;如果设置了O_CREAT标志,则继续查找路径最后一个分量
最后获取对应文件的打开文件结构
将这个结构与当前进程的打开文件表联系起来,返回相应的fd。
4. do_sys_open
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
struct open_flags op;
int fd = build_open_flags(flags, mode, &op);
struct filename *tmp; if (fd)
return fd; tmp = getname(filename);
if (IS_ERR(tmp))
return PTR_ERR(tmp); fd = get_unused_fd_flags(flags);
if (fd >= ) {
struct file *f = do_filp_open(dfd, tmp, &op);
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f);
fd_install(fd, f);
}
}
putname(tmp);
return fd;
}
open系统调用服务例程的核心为do_sys_open
4.1 do_filp_open
struct file *do_filp_open(int dfd, struct filename *pathname,
const struct open_flags *op)
{
struct nameidata nd;
int flags = op->lookup_flags;
struct file *filp; set_nameidata(&nd, dfd, pathname);
filp = path_openat(&nd, op, flags | LOOKUP_RCU);
if (unlikely(filp == ERR_PTR(-ECHILD)))
filp = path_openat(&nd, op, flags);
if (unlikely(filp == ERR_PTR(-ESTALE)))
filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
restore_nameidata();
return filp;
}
当内核要访问一个文件时,第一步需要找到这个文件,这由do_filp_open完成
在do_filp_open的实现中,查找文件过程由path_openat调用path_init和link_path_walk完成
这两个函数将用户传进来的用字符串表示的文件路径,转换成一个dentry结构,建立好相应的inode,并返回file对象
4.2 fd_install
void fd_install(unsigned int fd, struct file *file)
{
__fd_install(current->files, fd, file);
}
void __fd_install(struct files_struct *files, unsigned int fd,
struct file *file)
{
struct fdtable *fdt; might_sleep();
rcu_read_lock_sched(); while (unlikely(files->resize_in_progress)) {
rcu_read_unlock_sched();
wait_event(files->resize_wait, !files->resize_in_progress);
rcu_read_lock_sched();
}
/* coupled with smp_wmb() in expand_fdtable() */
smp_rmb();
fdt = rcu_dereference_sched(files->fdt);
BUG_ON(fdt->fd[fd] != NULL);
rcu_assign_pointer(fdt->fd[fd], file);
rcu_read_unlock_sched();
}
- do_sys_open完成以上处理后,将获取到的file结构体通过fd_install到当前进程的打开文件表中。其索引为fd
4.3 do_sys_open剩余操作
do_sys_open的剩余将进程关联的file的描述符返回用户
用户随后通过文件描述符,来访问这些数据结构
如上打开文件的核心是查找文件
5.查找文件
1. 打开文件的核心为查找,通常内核将查找过程分为两部分:
- 查找起始位置信息
主要是判断是系统根目录还是当前工作目录,以获取后面循环查找的起始位置(如/home/clj/file1.c中的“/”)
- 循环查找路径名后续分量
以起始位置开始,循环查找后续每个路径分量
2. 循环查找路径分量的过程,涉及多级cache.
循环查找后续路径分量,首先从dentry cache开始查找,在dentry cache中查找对应的dentry,若找到则直接返回;
若没有找到,则必须去底层文件系统查找对应的dentry

5.1 dentry cache的引入
由于块设备速度比较慢,可能需要很长时间才能找到与一个文件名关联的inode信息,所以引入dentry cache
5.2 dentry cache的描述
- 缓存的组织
散列表:包含了所有活动的dentry对象。散列表由dentry_hashtable组织,dentry通过d_hash连入散列表中;
LRU链表:dentry结构体中由d_lru链表组织。LRU链表中的元素同时也在dentry cache中;
- 缓存的查找
缓存由d_hash计算散列值,通过值对应的索引从dentry_hashtable中查找相应的队列;
再从队列头循环查找对应的dentry;
并将其从LRU中移除

图 dentry cache组织图
5.3 快速查找关键结构体qstr
struct qstr {
union {
struct {
HASH_LEN_DECLARE;
};
u64 hash_len;
};
const unsigned char *name;
};
5.4 dentry cache查找关键代码
struct dentry *__d_lookup(const struct dentry *parent, const struct qstr *name)
{
unsigned int len = name->len;
unsigned int hash = name->hash;
const unsigned char *str = name->name;
struct hlist_bl_head *b = d_hash(parent, hash);
struct hlist_bl_node *node;
struct dentry *found = NULL;
struct dentry *dentry; /*
* Note: There is significant duplication with __d_lookup_rcu which is
* required to prevent single threaded performance regressions
* especially on architectures where smp_rmb (in seqcounts) are costly.
* Keep the two functions in sync.
*/ /*
* The hash list is protected using RCU.
*
* Take d_lock when comparing a candidate dentry, to avoid races
* with d_move().
*
* It is possible that concurrent renames can mess up our list
* walk here and result in missing our dentry, resulting in the
* false-negative result. d_lookup() protects against concurrent
* renames using rename_lock seqlock.
*
* See Documentation/filesystems/path-lookup.txt for more details.
*/
rcu_read_lock(); hlist_bl_for_each_entry_rcu(dentry, node, b, d_hash) { if (dentry->d_name.hash != hash)
continue; spin_lock(&dentry->d_lock);
if (dentry->d_parent != parent)
goto next;
if (d_unhashed(dentry))
goto next; /*
* It is safe to compare names since d_move() cannot
* change the qstr (protected by d_lock).
*/
if (parent->d_flags & DCACHE_OP_COMPARE) {
int tlen = dentry->d_name.len;
const char *tname = dentry->d_name.name;
if (parent->d_op->d_compare(parent, dentry, tlen, tname, name))
goto next;
} else {
if (dentry->d_name.len != len)
goto next;
if (dentry_cmp(dentry, str, len))
goto next;
} dentry->d_lockref.count++;
found = dentry;
spin_unlock(&dentry->d_lock);
break;
next:
spin_unlock(&dentry->d_lock);
}
rcu_read_unlock(); return found;
}
Linux文件系统3--打开文件的更多相关文章
- Linux操作系统中打开文件数量的查看方法
Linux操作系统中打开文件数量的查看方法ulimit -n 4096也就是限制用户的最大文件打开数为4096个 在网上查了关于怎么查看文件打开数的文章大致有两种说法/proc/sys/fs/file ...
- 【Linux学习】Linux文件系统5—查看文件内容命令
Linux文件系统5-查看文件内容命令 cat: 由第一行开始显示文件内容 more: 一页一页地显示文件内容,空格键可以继续翻页显示下一页内容 less:与more类似,但是可以往前翻页 head: ...
- Linux下Firefox打开文件jnlp文件
ubuntu(linux)打开jnlp文件 咘咘 2019-05-20 15:12:48 1331 收藏展开 前提条件是安装有java环境.whereis java 查看自己java安装目录.本人是在 ...
- Linux文件系统与日志文件
目录 一.inode和block 1.1.inode和block概述 1.2.inode的内容 inode包含文件的元信息: 查看inode号两种方式 目录文件的结构 1.3.inode的号码 用户通 ...
- 深入理解Linux文件系统与日志文件
目录: 一.inode与block 二.inode内容 三.inode的号码 四.inode的大小 五.链接文件 六.inode节点耗尽故障处理 七.恢复EXT类型的文件编译安装extundelete ...
- Linux文件系统之删除文件、文件夹(rm,rmdir)
rm命令,rmdir命令 rm命令Remove,功能:1)删除目录,2)删除文件. (可以递归的删除指定目录的所有文件及子目录) 注意:rm是一个危险的命令,使用的时候要特别当心,尤其对于初学者来说 ...
- 【Linux】系统打开文件最大数量限制(进程打开的最大文件句柄数设置)
利用ulimit命令可以对资源的可用性进行控制. -H选项和-S选项分别表示对给定资源的硬限制(hard limit)和软限制(soft limit)进行设置. 硬限制(hard limit)一旦被设 ...
- Linux记录-lsof打开文件工具常用操作
lsof `which httpd` //那个进程在使用apache的可执行文件 lsof /etc/passwd //那个进程在占用/etc/passwd lsof /dev/hda6 //那个进程 ...
- Linux文件系统之复制文件cp(文件复制)
cp 命令(文件复制) cp命令用来将一个或多个源文件或者目录复制到指定的目的文件或目录.它可以将单个源文件复制成一个指定文件名的具体的文件或一个已经存在的目录下.cp命令还支持同时复制多个文件, ...
随机推荐
- 架构师成长之路6.4 DNS服务器搭建(部署主从DNS)
点击返回架构师成长之路 架构师成长之路6.3 DNS服务器搭建(部署主从DNS) 部署主DNS : 点击 部署从DNS : 如下步骤 1.与主DNS一样,安装bind yum -y install ...
- 【BZOJ5286】[HNOI2018]转盘(线段树)
[BZOJ5286][HNOI2018]转盘(线段树) 题面 BZOJ 洛谷 题解 很妙的一道题目啊.(全世界除了我这题都有40分,就我是一个状压选手 首先来发现一些性质,我们走一圈一定不会更差. 为 ...
- 【转】typedef和#define的用法与区别
typedef和#define的用法与区别 一.typedef的用法 在C/C++语言中,typedef常用来定义一个标识符及关键字的别名,它是语言编译过程的一部分,但它并不实际分配内存空间,实例像: ...
- 洛谷 P1879 [USACO06NOV]玉米田 解题报告
P1879 [USACO06NOV]玉米田Corn Fields 题目描述 农场主\(John\)新买了一块长方形的新牧场,这块牧场被划分成\(M\)行\(N\)列\((1 ≤ M ≤ 12; 1 ≤ ...
- SIEVE 线性筛
今天来玩玩筛 英文:Sieve 有什么筛? 这里介绍:素数筛,欧拉筛,约数个数筛,约数和筛 为什么要用筛? 顾名思义,筛就是要漏掉没用的,留下有用的.最终筛出来1~n的数的一些信息. 为什么要用线性筛 ...
- C# winform C/S WebBrowser 微信第三方登录
网上很多的资料都是B/S结构的,这里是基于C# C/S 结构的微信第三方授权登录 一.准备知识 1 http Get和Post方法.做第三方授权登录,获取信息基本上都是用get和post方法,做之前需 ...
- JAVA:当数据库重启后连接池没有自动识别的解决办法
今天发现服务器上的一个服务程序出现问题,软件抛出:Connection reset by peer: socket write error 无法正常提供服务,找了一下原因,原来是因为数据库服务器重启, ...
- es6中的函数
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面. function log(x, y = 'World') { console.log(x, y); } log('Hello') // ...
- asp.net 结合本地jQuery使在提交时显示错误提示
最近在做一个项目,做的表单有的比较长,如果直接点提交,错误提示有时可能用户看不见,对用户体验不好.还有客户端提交有点慢,担心用户重复提交,于是做了个检测用户提交表单验证是否有错误,没错误就提交,且把按 ...
- python 数据类型 datatype
python 数据类型 datatype 列表list 元组tuple 集合set 字典dictionary 字符串string 一.列表list list :python的一种数据类型.是可变的, ...