android程序間傳遞檔案描述符原理
在linux中,程序開啟一個檔案,返回一個整數的檔案描述符,然後就可以在這個檔案描述符上對該檔案進行操作。那麼檔案描述符和檔案到底是什麼關係?程序使用的是虛擬地址,不同程序間是地址隔離的,如何在兩個程序中傳遞檔案描述符,然後指向同一檔案(binder傳遞檔案描述符)?
linux開啟檔案過程
下圖是linux核心中開啟檔案的結構體之間的關係圖(只是大概,細節可以參考各種核心書籍):
核心中每個程序都使用task_struct結構體表示,其成員files是一個files_struct結構體,表示該程序開啟的所有檔案相關資訊;
files_struct結構體中的fd_array陣列(上層應用程式返回的檔案描述符其實就是這個陣列的下標),是個struct file,核心對開啟的檔案都用file結構體描述,而成員fdt是個struct fdtable,其成員fd代表陣列fd_array的起始地址;
struct file中的f_dentry表示檔案的dentry結構,而dentry中包含了檔案唯一的inode,即d_inode,d_inode唯一表示了該檔案(每個具體檔案只有一個inode結構,而多個dentry可以指向同一個inode,例如軟連結)。
下面簡要分析下程式碼。
上層:應用程式呼叫c庫中(實現依賴使用的c庫)的open()函式開啟一個檔案,open()返回一個檔案描述符;
核心:上層open呼叫系統呼叫open函式,
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
{
ret = do_sys_open(AT_FDCWD, filename, flags, mode);
return ret;
}
實際呼叫的是
long do_sys_open(int dfd, const char __user *filename, int flags, int mode) { struct open_flags op; int lookup = build_open_flags(flags, mode, &op); char *tmp = getname(filename); int fd = PTR_ERR(tmp); if (!IS_ERR(tmp)) { //① //獲取程序中未使用的fd,就是從0開始的整數,使用一個往上+1 fd = get_unused_fd_flags(flags); if (fd >= 0) { //② //構建file結構體 struct file *f = do_filp_open(dfd, tmp, &op, lookup); if (IS_ERR(f)) { put_unused_fd(fd); fd = PTR_ERR(f); } else { fsnotify_open(f); //③ //將fd和file結構體建立關係 fd_install(fd, f); } } putname(tmp); } return fd; }
void fd_install(unsigned int fd, struct file *file) { //當前程序的files_struct結構 struct files_struct *files = current->files; struct fdtable *fdt; spin_lock(&files->file_lock); //獲取files_struct中的fdt結構體 fdt = files_fdtable(files); BUG_ON(fdt->fd[fd] != NULL); //將fdt->fd[fd],也就是fd_array[fd],賦值為file, rcu_assign_pointer(fdt->fd[fd], file); spin_unlock(&files->file_lock); }
do_sys_open主要就完成了上面註釋的3步:
獲取程序中未使用的fd,就是從0開始的整數,使用一個往上+1;
根據檔案的路徑,構建file結構體,即構建其中的dentry等;
將fd、file結構體同本程序task_struct結構體建立關係。
經過上面的分析,我們可以得到一個結論:
核心用struct file描述檔案,在單個程序中,開啟的檔案都儲存在程序結構體task_struct中files_struct結構體中的fd_array陣列中,而上層返回的檔案描述符就是這個陣列的下標,用來和struct file保持對應關係。因此在不同的程序中開啟同一檔案,核心都會建立一個新的file結構用來和實際檔案關聯(但是同一檔案在不同程序中的file結構體主要內容是相同的,都描述了所指向的實際檔案),然後給上層返回不同的檔案描述符。
那麼如何將不同程序中的檔案描述符關聯為同一檔案?下面我們看看android binder通過傳遞檔案描述符如何實現同一檔案描述符的共享。
android binder傳遞檔案描述符
下面是核心binder_transaction函式中的一部分,
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
case BINDER_TYPE_FD: {
int target_fd;
struct file *file
//通過本程序的fd,獲取對應的file結構體
file = fget(fp->handle);
//獲取目標程序未使用的檔案描述符
target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
//將file結構和目標程序的fd等聯絡起來
task_fd_install(target_proc, target_fd, file);
fp->handle = target_fd;
} break;
}
上述主要工作和前面open函式的實現類似,只不過是去獲取目標程序的未使用檔案描述符,然後將本程序的file結構體去和目標程序掛鉤(file結構體主要內容描述的都是具體檔案相關的,例如dentry,所以不同程序間可以共享,因為都是指向同一實際檔案),這樣binder驅動就悄無聲息的幫我們在核心中在目標程序中新建了檔案描述符,並將原程序的file結構與之掛鉤,就像在目標程序中打開了原程序中的該檔案一樣,只不過返回給目標程序上層的描述符是新的target_fd。