目錄linux
在 Lab01 實驗中,twd2 認爲基於 close-then-dup
的方法實現的 dup2
是病態的,多線程下存在 race 風險。git
// from linux v0.11 fs/fcntl.c static int dupfd(unsigned int fd, unsigned int arg) { if (fd >= NR_OPEN || !current->filp[fd]) return -EBADF; if (arg >= NR_OPEN) return -EINVAL; while (arg < NR_OPEN) if (current->filp[arg]) arg++; else break; if (arg >= NR_OPEN) return -EMFILE; current->close_on_exec &= ~(1<<arg); (current->filp[arg] = current->filp[fd])->f_count++; return arg; } int sys_dup2(unsigned int oldfd, unsigned int newfd) { sys_close(newfd); return dupfd(oldfd, newfd); }
通過調查,我獲得一些結論,此處按時間順序還原。github
這個時候 POSIX Thread 還沒出來,所以 POSIX 標準中沒有 share file descriptor table 的狀況。該狀況描述可見於 linux/v5.3.12/fs/file.c#L851
。c#
通過閱讀代碼 glibc/sysdeps/posix/dup2.c
中的 dup2
函數 ,發現該文件就採用了 close-then-dup
的寫法實現 dup2 ;經參考 linux/v0.1.0/fs/fcntl.c
和 linux/v1.0.0/fs/fcntl.c
中的 sys_dup2
函數也採用了相同的作法。多線程
經考證,這些代碼都是 1995 年以前完成的。ide
因爲 POSIX Thread 的出現,內核須要處理多個線程同時訪問 file descriptor table 的狀況。此時內核中大部分訪問進程元數據的代碼都加了自旋鎖。例如 linux v5.3.12 中的實現方法:函數
static int do_dup2(struct files_struct *files, struct file *file, unsigned fd, unsigned flags) __releases(&files->file_lock) // 彷佛是一個 Annotation,用來標記這個函數會釋放某個鎖。加鎖是在 linux/v5.3.12/source/fs/file.c#L909 的 ksys_dup3 中完成的。 { struct file *tofree; struct fdtable *fdt; /* * We need to detect attempts to do dup2() over allocated but still * not finished descriptor. NB: OpenBSD avoids that at the price of * extra work in their equivalent of fget() - they insert struct * file immediately after grabbing descriptor, mark it larval if * more work (e.g. actual opening) is needed and make sure that * fget() treats larval files as absent. Potentially interesting, * but while extra work in fget() is trivial, locking implications * and amount of surgery on open()-related paths in VFS are not. * FreeBSD fails with -EBADF in the same situation, NetBSD "solution" * deadlocks in rather amusing ways, AFAICS. All of that is out of * scope of POSIX or SUS, since neither considers shared descriptor * tables and this condition does not arise without those. */ fdt = files_fdtable(files); tofree = fdt->fd[fd]; if (!tofree && fd_is_open(fd, fdt)) // 特殊狀況,只有 fd2 被其餘線程競爭時會出現;正確作法是調用 dup2 前不要 close(fd2) 便可。 goto Ebusy; get_file(file); rcu_assign_pointer(fdt->fd[fd], file); __set_open_fd(fd, fdt); if (flags & O_CLOEXEC) __set_close_on_exec(fd, fdt); else __clear_close_on_exec(fd, fdt); spin_unlock(&files->file_lock); // 釋放鎖 if (tofree) filp_close(tofree, files); return fd; Ebusy: spin_unlock(&files->file_lock); // 釋放鎖 return -EBUSY; }
鑑於目前我處於 MIT 6.828 Lab 01,所以 race 狀況不予考慮。彷佛在 MIT 6.828 課程中,也未涉及 thread 內容。後續 Lab 只讓設計了 user-space thread ,目測至關於 coroutine 。ui