内核源码:linux-4.4 目标平台:ARM体系结构 源码工具:source insight 4
说明: 文中由于 md 语法问题,无法在代码高亮的同时而忽略由于 __ 或者 * 造成斜体的 问题,所以类似 __user 改成 __ user,或者 char *filename 改成 char* filename。 通过在中间添加空格进行避免。注释统一使用了 \\。
open 对应的内核系统调用
应用层的 open 函数是 glibc 库封装了系统调用以比较友好的方式提供给开发者。 那么为什么要这么做? 这主要是从安全以及性能这两大方面进行了考虑:
在用户空间和内核空间之间,有一个叫做Syscall(系统调用, system call)的中间层,是连接用 户态和内核态的桥梁。这样即提高了内核的安全型,也便于移植, 只需实现同一套接口即可。Linux系统,用户空间通过向内核空间发出Syscall,产生软中断, 从而让程序陷入内核态,执行相应的操作。对于每个系统调用都会有一个对应的系统调用号 ,比很多操作系统要少很多。
安全性与稳定性:内核驻留在受保护的地址空间,用户空间程序无法直接执行内核代码 ,也无法访问内核数据,通过系统调用
性能:Linux上下文切换时间很短,以及系统调用处理过程非常精简,内核优化得好,所以性能上 往往比很多其他操作系统执行要好。
在应用层对于 open 操作主要使用的是以下两个函数:
(1) int open(const char *pathname, int flags, mode_t mode);
(2) int openat(int dirfd, const char *pathname, int flags, mode_t mode);
复制代码
如果打开文件成功,那么返回文件描述符,值大于或等于0;如果打开文件失败,返 回负的错误号。
下面是该函数参数的说明:
参数 pathname 是文件路径,可以是相对路径(即不以 “/” 开头),也可以是绝对路径(即以 “/” 开头)。
参数 dirfd 是打开一个目录后得到的文件描述符,作为相对路径的基准目录。如果文件路径是 相对路径,那么在函数 openat 中解释为相对文件描述符 dirfd 引用的目录,open 函数中解释为相对 调用进程的当前工作目录。如果文件路径是绝对路径, openat 忽略参数 dirfd。
参数 flags 必须包含一种访问模式: O_RDONLY (只读)、O_ WRONLY (只写)或 O_RDWR(读写)。参数 flags 可以包含多个文件创建标志和文件状态标志。 两组标志的区别是: 文件创建标志只影响打开操作, 文件状态标志影响后面的读写操作。
文件创建标志包括如下:
O_CLOEXEC:开启 close-on-exc标志,使用系统调用 execve() 装载程序的时候关闭文件。
CREAT:如果文件不存在,创建文件。
ODIRECTORY:参数 pathname 必须是一个日录。
EXCL:通常和标志位 CREAT 联合使用,用来创建文件。如果文件已经存在,那么 open() 失败,返回错误号 EEXIST。
NOFOLLOW:不允许参数 pathname 是符号链接,即最后一个分量不能是符号 链接,其他分量可以是符号链接。如果参数 pathname 是符号链接,那么打开失败,返回错误号 ELOOP。
O_TMPFILE:创建没有名字的临时普通文件,参数 pathname 指定目录关闭文件的时候,自动删除文件。
O_TRUNC:如果文件已经存在,是普通文件并且访问模式允许写,那么把文件截断到长度为0。
文件状态标志包括如下:
APPEND:使用追加模式打开文件,每次调用 write 写文件的时候写到文件的末尾。
O_ASYNC:启用信号驱动的输入输出,当输入或输出可用的时候,发送信号通知进程,默认的信号是 SIGIO。
O_DIRECT:直接读写存储设备,不使用内核的页缓存。虽然会降低读写速度, 但是在某些情况下有用处,例如应用程序使用自己的缓冲区,不需要使用内核的页缓存文件。
DSYNC:调用 write 写文件时,把数据和检索数据所需要的元数据写回到存储设备
LARGEFILE:允许打开长度超过 4 GB 的大文件。
NOATIME:调用 read 读文件时,不要更新文件的访问时间。
O_NONBLOCK:使用非阻塞模式打开文件, open 和以后的操作不会导致调用进程阻塞。
PATH:获得文件描述符有两个用处,指示在目录树中的位置以及执行文件描述符层次的操作。 不会真正打开文件,不能执行读操作和写操作。
O_SYNC:调用 write 写文件时,把数据和相关的元数据写回到存储设备。
参数 mode: 参数 mode 指定创建新文件时的文件模式。当参数 flags 指定标志位 O_CREAT 或 O_TMPFILE 的时候,必须指定参数 mode,其他情况下忽略参数 mode。 参数 mode 可以是下面这些标准的文件模式位的组合。
S_IRWXU(0700,以0开头表示八进制):用户(即文件拥有者)有读、写和执行权限。
S_IRUSR(00400):用户有读权限。
S_IWUSR(00200):用户有写权限
S_IXUSR(00100):用户有执行权限。
S_IRWXG(00070):文件拥有者所在组的其他用户有读、写和执行权限
S_IRGRP(00040):文件拥有者所在组的其他用户有读权限。
S_IWGRP(00020):文件拥有者所在组的其他用户有写权限。
S_IXGRP(0010):文件拥有者所在组的其他用户有执行权限。
S_IRWXO(0007):其他组的用户有读、写和执行权限。
S_IROTH(0004):其他组的用户有读权限。
S_IWOTH(00002):其他组的用户有写权限。
S_IXOTH(00001):其他组的用户有执行权限。
参数 mode 可以包含下面这些 Linux 私有的文件模式位:
S_ISUID (0004000):set-user-ID 位。
S_ISGID (0002000):set-group-iD位。
S_ISVTX(0001000):粘滞(sticky)位。
那么我们该如何找到对应的 syscall? 有几个小技巧可以用来帮助我们:
用户空间的方法xxx,对应系统调用层方法则是 sys_xxx;
unistd.h 文件记录着系统调用中断号的信息。
宏定义 SYSCALL_DEFINEx(xxx,…),展开后对应的方法则是 sys_xxx;
方法参数的个数x,对应于 SYSCALL_DEFINEx。
根据第一个小技巧,我们知道我们需要找的函数为:sys_open。 具体代码流程比较复杂,这里使用取巧的方式,找到对应的内核函数,前面提到需要找的的函数 为 sys_open。 这种函数在内核中是通过宏定义 SYSCALL_DEFINEx 展开后得到的。那么可以