目录
环境
- linux 4.19
很多注释都写在了代码里,就不单独总结出来放到文章中了,看一下代码+注释回忆一下即可
1.open()系统调用。
用户空间使用open(),最终调用到内核的函数如下,至于系统调用的过程,请看我的另外一篇博客:Linux内核学习之 – ARMv8架构的系统调用
fs/open.c:
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) // g,三个参数为filename, flags, mode
{
if (force_o_largefile()) // g, (BITS_PER_LONG != 32),其中BITS_PER_LONG应该是定义long类型的大小,32位架构long就是32,64位架构long是64
flags |= O_LARGEFILE; // g, 如果64位架构,补上O_LARGEFILE标志
return do_sys_open(AT_FDCWD, filename, flags, mode);
}
只调用了一个函数,就是do_sys_open(),进一步分析do_sys_open()函数:
fs/open.c:
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
struct open_flags op; // g,strcut open_flags这个结构体会包含一些控制flag,根据用户传入的参数进行初始化,之后open的操作就不再依赖于用户参数,而是依赖于这个结构体
int fd = build_open_flags(flags, mode, &op); // g, 根据传入的flagse会mode,来初始化op结构体,open的模式,flag等都在open_flags op结构体中
struct filename *tmp; // g, struct filename会在open过程中记录处理过后的文件名信息。之后open对h文件名的获取依赖于这个结构体
if (fd)
return fd;
// g, 根据一个字符串指针,返回一个struct filename*,该结构体的->name域所指内存存储着文件名
tmp = getname(filename);
if (IS_ERR(tmp))
return PTR_ERR(tmp);
fd = get_unused_fd_flags(flags); // g, 分配一个没用过的,符合要求的fd号,同时更新current->files->fdt里面的三个重要bitmap,有必要的话会分配新的占用更多内存的fdt替换原来的fdt
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, &op); // g, 该函数解析传入的文件路径名(主要是获取路径的最后一个分量对应的dentry以及inode),然后创建一个新的struct file结构体,使用找到的dentry和inode初始化这个file
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f); // g, 通知其他相关项如事件通知、audit机制、文件系统监控系统等等,该文件已经打开
fd_install(fd, f); // g, 把file和fd关联起来,建立了current(当前进程的tast_struct)->files->fdt->fd[fd]与f的联系(fdt->fd是个struct file**指针,其实就是current fd array)
}
}
putname(tmp);
return fd;
}
在这里做一个总结,do_sys_open()实现的功能可以概括为:
- 解析用户传入的各种参数,包括模式,权限,文件名
- 分配一个fd号
- 根据解析的参数,创建并初始化一个struct file结构体。
- 建立fd号与创建的struct file的联系,后续的write(),read()等可以直接通过fd找到对应的struct file
接下来对其中的几个重要函数进行分析,在此之前先整理一个调用层次:
do_sys_open()
-->build_open_flags() // 解析用户参数
-->getname() // 解析用户传入的文件名
-->getname_flags()
-->get_unused_fd_flags() // 获取一个未使用过的fd
-->__alloc_fd()
-->do_filp_open() // 打开文件,创建并初始化一个新的struct file结构体
-->set_nameidata()
-->path_openat()
-->path_init()
-->link_path_walk()
-->do_last()
-->fsnotify_open() // 通知其他相关项如事件通知、audit机制、文件系统监控系统等等,该文件已经打开
-->fd_install() // 建立分配的fd号与创建的struct file的关系
-->__fd_install()
所有函数调用并没有全部展示,只是列出了open()系统调用中几个重要的函数
1.1 getname()
该函数用来转换用户传入的路径名,解析用户传入的路径名来填充为一个内核中的结构体struct filename:
fs/namei.c:
getname(const char __user * filename)
{
return getname_flags(filename, 0, NULL);
}
其中只是单纯的调用了函数getname_flags():
fs/namei.c:
struct filename *
getname_flags(const char __user *filename, int flags, int *empty)
{
struct filename *result;
char *kname;
int len;
BUILD_BUG_ON(offsetof(struct filename, iname) % sizeof(long) != 0);
// g, 与linux的审计机制有关,会记录一些自己设置的规则信息,如果开启了此功能就可以从此模块获取filename结构体。
// g, 需要开启一个宏CONFIG_AUDITSYSCALL才有效果,看了下.config中CONFIG_AUDITSYSCALL is not set,所以暂时不去研究这个审计模块
result = audit_reusename(filename);
if (result)
return result;
result = __getname(); // g, 是个宏,= kmem_cache_alloc(names_cachep, GFP_KERNEL),该函数会从names_cachep的slab分配器里分配一个struct filename出来
if (unlikely(!result))
return ERR_PTR(-ENOMEM);
/*
* First, try to embed the struct filename inside the names_cache
* allocation
*/
kname = (char *)result->iname;
result->name = kname; // g, result->name和result->iname[]都将指向同一个char
len = strncpy_from_user(kname, filename, EMBEDDED_NAME_MAX); // g, 应该跟copy_from_user大同小异,是一个用户空间和内存空间的字符串拷贝函数,最大复制的字符数不能超过EMBEDDED_NAME_MAX
if (unlikely(len < 0)) {
__putname(result); // g, 如果有问题,直接释放这个slab
return ERR_PTR(len);
}
/* -------------------- 分割线,上面是文件名不太大的情况,下面是当文件名超过了EMBEDDED_NAME_MAX = 4096 - offsetof(struct filename, iname) 之后的操作
* Uh-oh. We have a name that's approaching PATH_MAX. Allocate a
* separate struct filename so we can dedicate the entire
* names_cache allocation for the pathname, and re-do the copy from
* userland.
*/
// g, note: likely和unlikely也有点说法,可以优化if和else的执行顺序
if (unlikely(len == EMBEDDED_NAME_MAX)) {
// g, 如果文件名字符串很长,已经 >= EMBEDDED_NAME_MAX了
// g, offsetof:返回iname[]在filenamed中的偏移值
const size_t size = offsetof(struct filename, iname[1]);
kname = (char *)result; // g, 使kname当temp指针记录一下result,暂时称为result_old
/*
* size is chosen that way we to guarantee that
* result->iname[0] is within the same object and that
* kname can't be equal to result->iname, no matter what.
*/
result = kzalloc(size, GFP_KERNEL); // g,分配一段&(struct filename.iname[1]) - &(struct filemae)这么大小的内存,此时result指向新的内存空间了,暂时称为result_new
if (unlikely(!result)) {
__putname(kname);
return ERR_PTR(-ENOMEM);
}
result->name = kname; // g,相当于result_new->name = result_old
len = strncpy_from_user(kname, filename, PATH_MAX); // g, 然后覆盖result_old,相当于把原来从slab中分配的内存全部当做文件名的缓存区了,result_old不再是一个struct filename结构体,而是变成了一段内存
if (unlikely(len < 0)) {
__putname(kname);
kfree(result);
return ERR_PTR(len);
}
if (unlikely(len == PATH_MAX)) {
// g, 文件名
__putname(kname);
kfree(result);
return ERR_PTR(-ENAMETOOLONG);
}
}
result->refcnt = 1; // g,可能是个标记位置
/* The empty path is special. */
if (unlikely(!len)) {
if (empty)
*empty = 1;
if