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

444 阅读5分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第5篇文章,点击查看活动详情

cstdio中的文件操作函数

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

代码参考:glibc源码下载:www.gnu.org/software/li…

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 }