From: http://blog.chinaunix.net/uid-24585858-id-2125500.html
open函数相信大家都用过,这里就不多说它的使用方法等事项,现直接进入正题...
用户态程序调用open函数时,会产生一个中断号为5的中断请求,其值以该宏__NR__open进行标示.而后该进程上下文(processcontext)将会被切换到内核空间。待内核中的相关操作完成后,就会从内核返回,此时还需要一次进程上下文切换(processcontext
switch)。
待进程执行流进入内核后,会通过一系列转换(这里我们不关心),最终进入SYSCALL_DEFINE3(open,...)函数中。看起来该函数定义比较特殊,其实SYSCALL_DRFINE3是一个宏,它被定义成如下形式:
|
#define SYSCALL_DEFINE3(name,
...) SYSCALL_DEFINEx(3, _##name,
__VA_ARGS__)
|
而
SYSCALL_DEFINEx宏具有如下形式:
|
#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中,而__SYSCALL_DEFINEx的定义如下:
|
#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 */
|
经过该宏的替换作用以后,最终我们就会得到sys_open或SYS_open所对应的函数原型。如下:
|
asmlinkage long sys_open(const char __user filename,
int flags,
int mode);
|
这也就是我们最常见到的open函数所对应的在内核中的实现部份。其实,对于linux下所有的系统调用函数,采用上述方法均可找到与其对应的内核函数sys_xxx().
接下来我们来看sys_open()函数。其实现如下:
|
SYSCALL_DEFINE3(open, const char __user
*, filename,
int, flags,
int, mode)
{
long ret;
if (force_o_largefile())
flags |= O_LARGEFILE;
ret = do_sys_open(AT_FDCWD, filename, flags,
mode);
/* avoid REGPARM breakage on x86:
*/
asmlinkage_protect(3,
ret, filename, flags, mode);
return ret;
}
|
在函数中首先调用force_o_largefile()宏进行LARGEFILE?确认。若是LARGEFILE则将其在flags中置位。随后调用主处理函数do_sys_open进行后续处理。其实,open的工作也就是在该函数中进行的。该函数原型如下:
|
long do_sys_open(int dfd,
const char __user
*filename,
int flags,
int mode);
|
在该函数中,如果通过getname()得到filename变量中文件名的指针没有错误的话,接下来就会掉用get_unused_fd_flags()函数获得一个没被使用的文件描述符fd。注意,对于文件描述符fd来讲,它只对本进程有效,也即它只在该进程中可见而在其它进程中代表着完全不同的文件。在32位系统中,一个进程最多打开32个文件,而在64位系统中可以打开64个文件。该函数就是用来获得一个未被使用的文件描述符fd.至于它的获取过程是很复杂的,这里不进行讲述。有兴趣的话,可以到www.kernel.org下载最新的内核源码进行研究。
在获得了有效的fd之后,我们通过do_filp_open()函数打开或者创建相应的文件,并且返回与之对应的文件结构structfile*f。如果函数返回的结构地址有效的话,那么就会调用fsnotify_open()函数(参数为f)将该文件加入到文件监控的系统中。该系统是用来监控文件被打开,创建,读写,关闭,修改等操作的,具体工作原理见后面文章。本文中不做讲述。随后调用fd_install()函数将structfile*f加入到fd索引位置处的数组中。如果后续过程中,有对该文件描述符的操作的话,就会通过查找该数组得到对应的文件结构,而后在进行相关操作。完成这些工作之后,open函数就返回了。返回值也就是刚才得到的fd.
那么,在do_filp_open()函数中有具体做了哪些工作呢?文件是如何被创建的呢?以及文件若存在的话,又是怎样被找到,而后被打开的呢?在中篇中我们将会回答这些问题。