1. 程式人生 > >android程序間傳遞檔案描述符原理

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。