iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 5
0
自我挑戰組

跨界的追尋:trace 30個基本Linux系統呼叫系列 第 5

trace 30個基本Linux系統呼叫第五日:close

前情提要

之前介紹了讀、寫、開啟等檔案操作,並且使用過終端機和ext4作為檔案開啟實例進行簡單的動態追蹤。


關閉

NAME
       close - close a file descriptor

SYNOPSIS
       #include <unistd.h>

       int close(int fd);

有開就有關。透過open呼叫取得的檔案描述子,可以透過close予以關閉。這個系統呼叫同樣被定義在fs/open.c之中:

1110 SYSCALL_DEFINE1(close, unsigned int, fd)
1111 {
1112         int retval = __close_fd(current->files, fd);
1113 
1114         /* can't restart close syscall because file table entry was cleared */
1115         if (unlikely(retval == -ERESTARTSYS ||
1116                      retval == -ERESTARTNOINTR ||
1117                      retval == -ERESTARTNOHAND ||
1118                      retval == -ERESTART_RESTARTBLOCK))
1119                 retval = -EINTR;
1120 
1121         return retval;
1122 }

從昨天追蹤open的經驗,我們可以預期close的操作應該也會涉及到程序的fstable結構,從裡面將指定的fd移除之類的。這個程式碼區段佔據視線的很大部份是關於錯誤回傳的狀況,這四種狀況都定在include/linux/errno.h,是核心內部使用的flag,回傳的則是標準的-EINTR,代表這個關閉檔案描述子的過程中被中斷了。

主體的部份在於1112行的__close_fd呼叫。這個呼叫取得當前程序的開啟檔案狀態,以及傳入欲關閉的檔案描述子。這個意圖很明顯,所以我們繼續追蹤下去:

635 int __close_fd(struct files_struct *files, unsigned fd)
636 {        
637         struct file *file;
638         struct fdtable *fdt;
639 
640         spin_lock(&files->file_lock);
641         fdt = files_fdtable(files);
642         if (fd >= fdt->max_fds)
643                 goto out_unlock;
644         file = fdt->fd[fd];
645         if (!file)
646                 goto out_unlock;
647         rcu_assign_pointer(fdt->fd[fd], NULL);
648         __clear_close_on_exec(fd, fdt);
649         __put_unused_fd(files, fd);
650         spin_unlock(&files->file_lock);
651         return filp_close(file, files);
652 
653 out_unlock:
654         spin_unlock(&files->file_lock);
655         return -EBADF;
656 }

641行的fdt取得,之前在read的時候有看到過struct fdtable *fdt = rcu_dereference_raw(files->fdt);這樣的寫法,這裡的files_fdtable也是一個最後會呼叫到rcu_dereference系列的巨集,實際上處理的事情多了些檔案是否上鎖的判斷。647行透過rcu機制清空fdt->fd[fd],至此,對這個程序而言,fd就已經不連結到任何真正存在的檔案了。接著到650行的unlock為止,這個程序對於開啟檔案的狀態修改告一段落,整個上鎖的區間就是一個critical section。

在這之前的部份相當於是核心迫使一個程序修正自己對於自己已開啟檔案的紀錄,而這之後的部份就是那個原本被使用的檔案如今要修正自己的資料結構。這個過程最後會回傳filp_close的執行結果,在同一個檔案之內,

1083 int filp_close(struct file *filp, fl_owner_t id)
1084 {
1085         int retval = 0;
1086 
1087         if (!file_count(filp)) {
1088                 printk(KERN_ERR "VFS: Close: file count is 0\n");
1089                 return 0;
1090         }
1091 
1092         if (filp->f_op->flush)
1093                 retval = filp->f_op->flush(filp, id);
1094 
1095         if (likely(!(filp->f_mode & FMODE_PATH))) {
1096                 dnotify_flush(filp, id);
1097                 locks_remove_posix(filp, id);
1098         }
1099         fput(filp);
1100         return retval;
1101 }

這裡也有物件導向的寫法應用在filp自帶的flush方法,但使用動態追蹤之後,無論是終端機或是ext4的關閉都不會觸發這個flush方法,因為這兩個系統都沒有定義這個方法,也許其他類型的裝置會比較需要這個方法的實作吧。最後,FMODE_PATH相關的判斷較可能發生,其內的dnotify_flush函數定義在fs/notify/dnotify/dnotify.c之中,註解明確地說每一次有檔案要關閉時必須呼叫。dnotify是一個知會目錄結構的機制。


結論

本文的內容比較貧乏一點,因為沒有繼續深究其他的底層機制。下一篇即將迎來通用檔案處理的最後一個系統呼叫ioctl,並將順便深入挖掘與終端機相關的部份。之後則是以fork為首的程序管理篇。我們明天再見!


上一篇
trace 30個基本Linux系統呼叫第四日:open
下一篇
trace 30個基本Linux系統呼叫第六日:ioctl與終端機
系列文
跨界的追尋:trace 30個基本Linux系統呼叫30

尚未有邦友留言

立即登入留言