写点什么

C++ 学习 ---cstdio 的源码学习分析 02- 文件删除函数 remove

作者:桑榆
  • 2022 年 9 月 17 日
    广东
  • 本文字数:3446 字

    阅读完需:约 11 分钟

cstdio 中的文件操作函数

stdio.h 中定义了文件删除函数 remove,文件重命名函数 rename,打开临时文件函数 tmpfile,生成临时文件名函数 tmpnam。接下来我们一起来分析一下 remove 对应的源码实现。

代码参考:glibc 源码下载:https://www.gnu.org/software/libc/libc.html

git clone https://sourceware.org/git/glibc.gitcd glibcgit 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_SYSCALL234 #define INTERNAL_SYSCALL(name, nr, args...)             \235     internal_syscall##nr (SYS_ify (name), args)
283 #undef internal_syscall3284 #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.h148 #ifdef __USE_ATFILE149 # define AT_FDCWD       -100    /* Special value used to indicate               150                        the *at functions should use the151                        current working directory. */152 # define AT_SYMLINK_NOFOLLOW    0x100   /* Do not follow symbolic links.  */153 # define AT_REMOVEDIR       0x200   /* Remove directory instead of154                        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.c1 #define IS_NO_DIRECTORY_ERROR errno != EISDIR2 #include <sysdeps/posix/remove.c>
//glibc/sysdeps/posix/remove.c24 #ifndef IS_NO_DIRECTORY_ERROR25 # 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 }
复制代码


发布于: 刚刚阅读数: 4
用户头像

桑榆

关注

北海虽赊,扶摇可接;东隅已逝,桑榆非晚! 2020.02.29 加入

Android手机厂商-相机软件系统工程师 爬山/徒步/Coding

评论

发布
暂无评论
C++学习---cstdio的源码学习分析02-文件删除函数remove_c++_桑榆_InfoQ写作社区