首先,linux内核的open函数是这么定义的SYSCALL_DEFINE3(open, ...),可以查到的宏定义为
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
然后由:
#ifdef CONFIG_FTRACE_SYSCALLS
#define SYSCALL_DEFINEx(x, sname, ...) \
static const char *types_##sname[] = { \
__SC_STR_TDECL##x(__VA_ARGS__) \
}; \
static const char *args_##sname[] = { \
__SC_STR_ADECL##x(__VA_ARGS__) \
}; \
SYSCALL_METADATA(sname, x); \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#else
#define SYSCALL_DEFINEx(x, sname, ...) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#endif
转换为:
__SYSCALL_DEFINEx(3,_open,__VA_ARGS__)
紧接着再次由:
#ifdef CONFIG_HAVE_SYSCALL_WRAPPERS
#define SYSCALL_DEFINE(name) static inline long SYSC_##name
#define __SYSCALL_DEFINEx(x, name, ...) \
asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)); \
static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__)); \
asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__)) \
{ \
__SC_TEST##x(__VA_ARGS__); \
return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__)); \
} \
SYSCALL_ALIAS(sys##name, SyS##name); \
static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))
#else /* CONFIG_HAVE_SYSCALL_WRAPPERS */
#define SYSCALL_DEFINE(name) asmlinkage long sys_##name
#define __SYSCALL_DEFINEx(x, name, ...) \
asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
#endif /* CONFIG_HAVE_SYSCALL_WRAPPERS */
转换为:
asmlinkage long sys_open(__SC_DECL3(__VA_ARGS__))
(t3 a3,__SC_DECL2(__VA_ARGS__))
(t3 a3,t2 a2,t1 a1)
static inline long SYSC_open(t3 a3,t2 a2,t1 a1);
asmlinkage long SyS_open()
{
__SC_TEST(t3);
__SC_TEST(t2);
__SC_TEST(t1);
return (long) SYSC_open((t3) a3,(t2) a2,(t1) a1);
}
不再一一展开。总之最后变为
do_sys_open(AT_FDCWD, filename, flags, mode)
首先是由build_open_flags(flags, mode, &op);处理打开文件时所附带的标志。
别的不多说,就说一个在这个函数里进行的操作:
op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;
去查了一下O_PATH标志并不是经常使用,而使用该标志一般意味着 to perform operations that act purely at the file descriptor level. The file itself is not opened。也就是说使用该标志并不会真正打开该文件。所以正常情况下op->intent = LOOKUP_OPEN
...
tmp = getname(filename);
...
fd = get_unused_fd_flags(flags);
fd = start;
if (fd < files->next_fd)
fd = files->next_fd;
if (fd < fdt->max_fds)
fd = find_next_zero_bit(fdt->open_fds, fdt->max_fds, fd);
...
if (start <= files->next_fd)
files->next_fd = fd + 1;
从上面这段代码可以看出,files->next_fd记录的是当前可以使用的最小的fd。注意find_next_zero_bit是从fd的基础上开始找的第一个可用的fd。所以如果start这次小于files->next_fd,就说明这次的fd是从files->next_fd开始找的,那么下次可用的最小的files->next_fd自然是这次分配到的fd加1(当然不保证一定可用,需要find_next_zero_bit进行验证)。
fd分配成功后,就要真正进行打开文件的操作了:
struct file *f = do_filp_open(dfd, tmp, &op);
可以看到在该函数内部创建了一个file结构体,以便为了后来表示打开的文件:
struct file *filp;
filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
上述标志中增加了一个LOOKUP_RCU,似乎RCU机制可以加快操作。后文将默认使用RCU机制
file = get_empty_filp();
nd->last_type = LAST_ROOT; /* if there are only slashes... */
nd->flags = flags | LOOKUP_JUMPED | LOOKUP_PARENT;
nd->depth = 0;
nd->base = NULL;
if (*name=='/') {
...
}
else if (dfd == AT_FDCWD)
...
}
路径名分两种情况,一种是绝对路径,一种是相对路径。这里都给出了处理分支。解释一下struct nameidata *nd这个结构体。这个结构体从这开始贯穿整个open函数,代表着当前正在操作或即将操作的dentry及其他一些相关信息。假设路径名为相对路径,那么可见nd->path = fs->pwd;。即被赋值为当前目录。这些处理完后,就可以进入link_path_walk(name, nd);该函数遍历路径各个分支,直到取到最后一个dentry或出错啥的。
link_path_walk(name,nd){
for(;;){
may_lookup(nd);{//权限检查
inode_permisssion(nd->inode,MAY_EXEC|MAY_NOT_BLOCK);
}
type = LAST_NORM;
if(name[0] == '.'){
//如果碰上了".."或".",那么type=LAST_DOTDOT或LAST_DOT
}
if(type == LAST_NORM){
struct dentry *parent = nd->path.dentry;
nd->flags &= ~LOOKUP_JUMPED;
}
nd->last.hash_len = hash_len;
nd->last.name = name;
nd->last_type = type;
name += hashlen_len(hash_len);
...//经过一番处理,walk刚刚得到的dentry
walk_component(truct nameidata *nd, struct path *path,int follow){
if (nd->last_type != LAST_NORM)
return handle_dots(nd, nd->last_type);
err = lookup_fast(nd, path, &inode);{
//首先在dentry_hashtable内部进行一次快速查找,从这次查找可以看出在该parent下其子dentry组成了一个hash_list
dentry = __d_lookup_rcu(parent, &nd->last, &seq);
path->mnt = mnt;
path->dentry = dentry;
return 0;
//当然刚才也可能出现在缓存的dentry_hashtable找不到的情况。
unlazy_walk(nd, dentry);//rcu to ref I think it's fast change to slow
...
return 1;//need_lookup
}
if (err){
err = lookup_slow(nd, path);
}
if (!inode || d_is_negative(path->dentry)) //当然也可能存在所要求的dentry不存在的情况:因为创建文件或恶意操作?
goto out_path_put;
...//follow link?
path_to_nameidata(path, nd);
nd->inode = inode;
return 0;
}
if (!d_can_lookup(nd->path.dentry)) { //如果该dentry不是目录
err = -ENOTDIR;
break;
}
}
terminate_walk(nd);//如果是RCU过程,释放RCU_LOCK
}
上面这段代码去掉了link_path_walk一些不太重要的部分,并且对有些比较重要的函数调用直接附在该函数调用后面进行解释,同时走的是RCU路径,。通过上面代码,如果不发生意外错误,一般都会使得nd的dentry为路径名的最后一部分的父目录,并且nd->name为路径名的最后一部分。
做完这些操作后,进入do_last(nd, &path, file, op, &opened, pathname);其中opend=0。如函数名提示,开始进入函数的最后一部分了。
static int do_last(struct nameidata *nd, struct path *path,
struct file *file, const struct open_flags *op,
int *opened, struct filename *name){
if (nd->last_type != LAST_NORM) { //如果是.或..
error = handle_dots(nd, nd->last_type);
goto finish_open;
}
if (!(open_flag & O_CREAT)) { //处理非创建的情况
error = lookup_fast(nd, path, &inode);
}
else{
error = complete_walk(nd);
...
}
retry_lookup:
if (op->open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) { //创建操作需要当前挂载的文件系统具有写权限
error = mnt_want_write(nd->path.mnt);
}
error = lookup_open(nd, path, file, op, got_write, opened);{
dentry = lookup_dcache(&nd->last, dir, nd->flags, &need_lookup);
if (!need_lookup && dentry->d_inode)
goto out_no_open;
if ((nd->flags & LOOKUP_OPEN) && dir_inode->i_op->atomic_open) {
return atomic_open(nd, dentry, path, file, op, got_write,
need_lookup, opened);{
if (((open_flag & (O_CREAT | O_TRUNC)) ||
(open_flag & O_ACCMODE) != O_RDONLY) && unlikely(!got_write))//看清楚这个逻辑判断操作,先或操作再且操作,主要是希望有写权限,但是可能没有got_write
}
}
}
}
因为标志是O_CREAT,所以会进入else分支,然后接下来处理创建的一些工作。其中一般都会进入atomic_open部分。该函数的注释说如果返回0,代表此次创建成功,且file结构体成功关联到此次创建的文件,如果返回1,则此次只做了查找操作,具体的打开文件操作仍然要交给调用者。别的情况就返回错误。
最后一些权限检查后,基本就完成了open函数的操作。
这其中对元数据查询的最关键的部分应该是在link_path_walk