cstdio 中的文件操作函数
stdio.h 中定义了文件删除函数 remove,文件重命名函数 rename,打开临时文件函数 tmpfile,生成临时文件名函数 tmpnam。接下来我们一起来分析一下 remove 对应的源码实现。
代码参考:glibc 源码下载:https://www.gnu.org/software/libc/libc.html
git clone https://sourceware.org/git/glibc.git
cd glibc
git checkout master
复制代码
代码路径:glibc/libio/stdio.h
文件删除函数---remove
删除指定文件名的文件,返回 int 类型,0 代表 success,其它数字同 linux 的异常 errorno
int remove ( const char * filename );
复制代码
代码实现:glibc/sysdeps/posix/remove.c 逻辑也相对简单,首先__unlink(file),这里实际上是删除文件,但是有可能失败,比如这个 file 其实是一个文件夹,那么这时我们调用 IS_NO_DIRECTORY_ERROR 宏进行判断__unlink 产生的 errno,如果宏值返回为 0(说明是一个文件夹),那么我们调用__rmdir 删除文件夹。
29 int
30 remove (const char *file)
31 {
32 /* First try to unlink since this is more frequently the necessary action. */
33 if (__unlink (file) != 0
34 /* If it is indeed a directory... */
35 && (IS_NO_DIRECTORY_ERROR
36 /* ...try to remove it. */
37 || __rmdir (file) != 0))
38 /* Cannot remove the object for whatever reason. */
39 return -1;
40
41 return 0;
42 }
复制代码
__unlink 的实现
有两种实现方式
unix 内核---INLINE_SYSCALL 实现
glibc/sysdeps/unix/sysv/linux/generic/unlink.c 中调用了系统 syscall 的方式,调用 unlinkat 进行实现
22 /* Remove the link named NAME. */
23 int
24 __unlink (const char *name)
25 {
26 return INLINE_SYSCALL (unlinkat, 3, AT_FDCWD, name, 0);
27 }
复制代码
INLINE_SYSCALL 的调用如下,实际上还是借助了 INTERNAL_SYSCALL 完成
38 /* Define a macro which expands into the inline wrapper code for a system
39 call. It sets the errno and returns -1 on a failure, or the syscall
40 return value otherwise. */
41 #undef INLINE_SYSCALL
42 #define INLINE_SYSCALL(name, nr, args...) \
43 ({ \
44 long int sc_ret = INTERNAL_SYSCALL (name, nr, args); \
45 __glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (sc_ret)) \
46 ? SYSCALL_ERROR_LABEL (INTERNAL_SYSCALL_ERRNO (sc_ret)) \
47 : sc_ret; \
48 })
复制代码
INTERNAL_SYSCALL 的调用,以 x86_64 架构的举例如下:首先调用 SYS_ify 将 unlinkat 拼接为__NR_unlinkat,注意这里调用的 nr 号为 3,所以实际调用的为 internal_syscall3,有 4 个参数,number 是__NR_unlinkat,arg1,arg2,arg3 依次对应上面传入的 AT_FDCWD, name, 0, 这里面最重要的是我们想要删除的文件名 name。
然后使用内嵌汇编语法,依次将参数放置到寄存器 rdi、rsi、rdx 中,然后调用 syscall 汇编指令,进而实现文件删除。
//glibc/sysdeps/unix/sysv/linux/x86_64/sysdep.h
29 /* For Linux we can use the system call table in the header file
30 /usr/include/asm/unistd.h
31 of the kernel. But these symbols do not follow the SYS_* syntax
32 so we have to redefine the `SYS_ify' macro here. */
33 #undef SYS_ify
34 #define SYS_ify(syscall_name) __NR_##syscall_name
233 #undef INTERNAL_SYSCALL
234 #define INTERNAL_SYSCALL(name, nr, args...) \
235 internal_syscall##nr (SYS_ify (name), args)
283 #undef internal_syscall3
284 #define internal_syscall3(number, arg1, arg2, arg3) \
285 ({ \
286 unsigned long int resultvar; \
287 TYPEFY (arg3, __arg3) = ARGIFY (arg3); \
288 TYPEFY (arg2, __arg2) = ARGIFY (arg2); \
289 TYPEFY (arg1, __arg1) = ARGIFY (arg1); \
290 register TYPEFY (arg3, _a3) asm ("rdx") = __arg3; \
291 register TYPEFY (arg2, _a2) asm ("rsi") = __arg2; \
292 register TYPEFY (arg1, _a1) asm ("rdi") = __arg1; \
293 asm volatile ( \
294 "syscall\n\t" \
295 : "=a" (resultvar) \
296 : "0" (number), "r" (_a1), "r" (_a2), "r" (_a3) \
297 : "memory", REGISTERS_CLOBBERED_BY_SYSCALL); \
298 (long int) resultvar; \
299 })
复制代码
其中参数宏的定义及意义如下:AT_FDCWD 表示使用当前工作目录,AT_REMOVEDIR 表示删除文件夹,0 默认删除文件。
//glibc/io/fcntl.h
148 #ifdef __USE_ATFILE
149 # define AT_FDCWD -100 /* Special value used to indicate
150 the *at functions should use the
151 current working directory. */
152 # define AT_SYMLINK_NOFOLLOW 0x100 /* Do not follow symbolic links. */
153 # define AT_REMOVEDIR 0x200 /* Remove directory instead of
154 unlinking file. */
复制代码
mach 内核实现
Mach 是一个由卡内基梅隆大学开发的用于支持操作系统研究的操作系统内核。
这里我们对这种实现方式做初步了解,就不深入讲解了。实现如下,实际上也是通过内核提供的函数或者结构实现文件的 unlink,通过调用__dir_unlink 实现:
//sysdeps/mach/hurd/unlink.c
24 /* Remove the link named NAME. */
25 int
26 __unlink (const char *name)
27 {
28 error_t err;
29 file_t dir;
30 const char *file;
31
32 dir = __directory_name_split (name, (char **) &file);
33 if (dir == MACH_PORT_NULL)
34 return -1;
35
36 err = __dir_unlink (dir, file);
37 __mach_port_deallocate (__mach_task_self (), dir);
38
39 if (err)
40 return __hurd_fail (err);
41 return 0;
42 }
复制代码
IS_NO_DIRECTORY_ERROR 宏的含义
有两处定义这个宏的地方,查看代码和文件包含关系之后可以看到其实实际上使用的是 glibc/sysdeps/unix/sysv/linux/remove.c 中的定义
即 errno != EISDIR,EISDIR 表示当前是文件夹,说明只有前面删除文件出错,且错误码表示为 EISDIR 时,IS_NO_DIRECTORY_ERROR 宏才返回 false,然后或运算短路,这才会执行后面的删除文件夹指令
//glibc/sysdeps/unix/sysv/linux/remove.c
1 #define IS_NO_DIRECTORY_ERROR errno != EISDIR
2 #include <sysdeps/posix/remove.c>
//glibc/sysdeps/posix/remove.c
24 #ifndef IS_NO_DIRECTORY_ERROR
25 # define IS_NO_DIRECTORY_ERROR errno != EPERM
26 #endif
复制代码
__rmdir 的实现
同样有两种实现方式
unix 内核---INLINE_SYSCALL 实现
注意,这里使用的还是 internal_syscall3,但是 arg3 有变化,相比删除文件的 0 变化到了 AT_REMOVEDIR,删除文件夹
//glibc/sysdeps/unix/sysv/linux/generic/rmdir.c
22 /* Remove the directory PATH. */
23 int
24 __rmdir (const char *path)
25 {
26 return INLINE_SYSCALL (unlinkat, 3, AT_FDCWD, path, AT_REMOVEDIR);
27 }
复制代码
mach 内核实现
最终调用__dir_rmdir 实现,不深入分析了
//glibc/sysdeps/mach/hurd/rmdir.c
23 /* Remove the directory FILE_NAME. */
24 int
25 __rmdir (const char *file_name)
26 {
27 error_t err;
28 const char *name;
29 file_t parent = __directory_name_split (file_name, (char **) &name);
30 if (parent == MACH_PORT_NULL)
31 return -1;
32 err = __dir_rmdir (parent, name);
33 __mach_port_deallocate (__mach_task_self (), parent);
34 if (err)
35 return __hurd_fail (err);
36 return 0;
37 }
复制代码
评论