linux檔案開啟和讀寫流程代碼解析__linux

來源:互聯網
上載者:User

開啟檔案流程:系統調用

fd=open("/dev/pcie_ssd",O_RDWR);


代碼定位fs: open.c檔案裡

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;


return do_sys_open(AT_FDCWD, filename, flags, mode);
}


long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
struct open_flags op;
int fd = build_open_flags(flags, mode, &op);
struct filename *tmp;


if (fd)
return fd;
tmp = getname(filename);
//擷取檔案名稱,其內部先建立存取檔案名稱的空間,然後從使用者空間將檔案名稱拷貝過來
if (IS_ERR(tmp))
return PTR_ERR(tmp);


fd = get_unused_fd_flags(flags);
/*擷取一個可用的fd,調用alloc_fd從fd_table中擷取一個可用的fd並簡單初始化,注意對於fd,
它只對本進程有效,也就是它只在該進程中可見而在其他進程中代表完全不同的檔案*/
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, &op);//建立file對象,進行路徑解析
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f);
/*檔案已經被開啟了,調用它,根據inode所指定的資訊進行開啟函數,
將該檔案加入到檔案監控的系統中,該系統是用來監控檔案被開啟、建立、讀寫、關閉、修改等操作的*/
fd_install(fd, f);
/*將檔案指標安裝到fd數組,將f加入到fd索引位置的數組中,
如果後續過程有對f繼續操作的話,就會通過尋找該數組得到對應的檔案結構,進行操作*/
}
}
putname(tmp);
return fd;

}

struct file *do_filp_open(int dfd, struct filename *pathname,
const struct open_flags *op)
{
struct nameidata nd;
int flags = op->lookup_flags;
struct file *filp;

set_nameidata(&nd, dfd, pathname);
filp = path_openat(&nd, op, flags | LOOKUP_RCU);/*根據op的look_up方法解析路路路徑*/
if (unlikely(filp == ERR_PTR(-ECHILD)))
filp = path_openat(&nd, op, flags);
if (unlikely(filp == ERR_PTR(-ESTALE)))
filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
restore_nameidata();
return filp;
}

int vfs_open(const struct path *path, struct file *file,
    const struct cred *cred)
{
struct dentry *dentry = path->dentry;/*根據路徑名解析dentry*/
struct inode *inode = dentry->d_inode;/*根據dentry找到inode*/


file->f_path = *path;
if (dentry->d_flags & DCACHE_OP_SELECT_INODE) {
inode = dentry->d_op->d_select_inode(dentry, file->f_flags);
if (IS_ERR(inode))
return PTR_ERR(inode);
}


return do_dentry_open(file, inode, NULL, cred);
}

static int do_dentry_open(struct file *f, struct inode *inode,int (*open)(struct inode *, struct file *),

 const struct cred *cred)

{

........

f->f_op = fops_get(inode->i_fop);//關鍵,根據inode的i_fop找到檔案的f_op

if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
i_readcount_inc(inode);
if ((f->f_mode & FMODE_READ) &&
    likely(f->f_op->read || f->f_op->read_iter))
f->f_mode |= FMODE_CAN_READ;
if ((f->f_mode & FMODE_WRITE) &&
    likely(f->f_op->write || f->f_op->write_iter))
f->f_mode |= FMODE_CAN_WRITE;

}

路徑尋找 path_lookup  見連結 http://blog.csdn.net/kickxxx/article/details/9529961

nameidata資料結構

尋找過程涉及到很多函數調用,在這些調用過程中,nameidata起到了很重要的作用:1. 向尋找函數傳遞參數;2. 儲存尋找結果。

[html]  view plain  copy struct nameidata {           struct dentry   *dentry;           struct vfsmount *mnt;           struct qstr     last;           unsigned int    flags;           int             last_type;           unsigned        depth;           char *saved_names[MAX_NESTED_LINKS + 1];              /* Intent data */           union {                   struct open_intent open;           } intent;   };  

VFS讀寫流程:系統調用

retval1=read(fd,buf,4096);retval1=write(fd,buf,4096);

代碼定位fs: read_write.c檔案裡


SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count)
{
struct fd f = fdget_pos(fd);//擷取file
ssize_t ret = -EBADF;


if (f.file) {
loff_t pos = file_pos_read(f.file); //讀取檔案讀寫位置
ret = vfs_write(f.file, buf, count, &pos);//VFS 讀檔案
if (ret >= 0)
file_pos_write(f.file, pos);  //回寫檔案讀寫位置
fdput_pos(f);
}


return ret;

}


ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;


if (!(file->f_mode & FMODE_WRITE)) //判斷檔案是否可寫
return -EBADF;
if (!(file->f_mode & FMODE_CAN_WRITE)) //是否定義檔案寫方法
return -EINVAL;
if (unlikely(!access_ok(VERIFY_READ, buf, count)))
return -EFAULT;


ret = rw_verify_area(WRITE, file, pos, count);//寫校正
if (ret >= 0) {
count = ret;
file_start_write(file);
ret = __vfs_write(file, buf, count, pos);//調用檔案寫操作方法
if (ret > 0) {
fsnotify_modify(file);//變更檔位置
add_wchar(current, ret);
}
inc_syscw(current);
file_end_write(file);
}


return ret;
}


ssize_t __vfs_write(struct file *file, const char __user *p, size_t count,
   loff_t *pos)
{
if (file->f_op->write)
return file->f_op->write(file, p, count, pos);//調用檔案寫操作方法
  //註:從這可以看出vfs沒有對寫入檔案內容進程緩衝
else if (file->f_op->write_iter)
return new_sync_write(file, p, count, pos);//通用檔案模型寫方法
else
return -EINVAL;
}


static ssize_t new_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len };
struct kiocb kiocb;
struct iov_iter iter;
ssize_t ret;


init_sync_kiocb(&kiocb, filp);//初始化非同步I/O的結構體

     /*struct kiocb {
struct file *ki_filp;  //檔案指標
loff_t ki_pos;//檔案位置
void (*ki_complete)(struct kiocb *iocb, long ret, long ret2);//回調,表示非同步I/O完成
void *private;
int ki_flags;
       ;*/
kiocb.ki_pos = *ppos;
iov_iter_init(&iter, WRITE, &iov, 1, len);


ret = filp->f_op->write_iter(&kiocb, &iter);
BUG_ON(ret == -EIOCBQUEUED);
if (ret > 0)
*ppos = kiocb.ki_pos;
return ret;
}


struct file_operations {
struct module *owner;、//第一個 file_operations 成員根本不是一個操作; 它是一個指向擁有這個結構的模組的指標. 這個成員用來在它的操作還在被使用時阻止模組被卸載. 幾乎所有時間中, 它被簡單初始化為 THIS_MODULE, 一個在 <Linux/module.h> 中定義的宏.
loff_t (*llseek) (struct file *, loff_t, int);//llseek 方法用作改變檔案中的當前讀/寫位置, 並且新位置作為(正的)傳回值. loff_t 參數是一個"long offset", 並且就算在 32位平台上也至少 64 位元寬. 錯誤由一個負傳回值指示. 如果這個函數指標是 NULL, seek 調用會以潛在地無法預知的方式修改 file 結構中的位置計數器( 在"file 結構" 一節中描述).
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//用來從裝置中擷取資料. 在這個位置的一個null 指標導致 read 系統調用以 -EINVAL("Invalid argument") 失敗. 一個非負傳回值代表了成功讀取的位元組數( 傳回值是一個 "signed size" 類型, 常常是目標平台本地的整數類型).
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//發送資料給裝置. 如果 NULL, -EINVAL 返回給調用 write 系統調用的程式. 如果非負, 傳回值代表成功寫的位元組數.
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);//對於裝置檔案這個成員應當為 NULL; 它用來讀取目錄, 並且僅對檔案系統有用.
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
//同步讀寫:應用程式發起讀寫後,等到讀寫函數完成返回,才能繼續跑後面的代碼。 
//非同步讀寫:應用程式發起讀寫後,將讀寫註冊到隊列,然後立馬返回,應用程式繼續跑後面的代碼,速度非常快,當讀寫動作完成後,系統發訊息通知應用程式,然後應用程式接收讀寫結果。根據情境選擇,驅動可以同時都實現同步和非同步介面。
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);//poll 方法是 3 個系統調用的後端: poll, epoll, 和 select, 都用作查詢對一個或多個檔案描述符的讀或寫是否會阻塞. poll 方法應當返回一個位元遮罩指示是否非阻塞的讀或寫是可能的, 並且, 可能地, 提供給核心資訊用來使調用進程睡眠直到 I/O 變為可能. 如果一個驅動的 poll 方法為 NULL, 裝置假定為不阻塞地可讀可寫.
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//ioctl 系統調用提供了發出裝置特定命令的方法(例如格式化磁碟片的一個磁軌, 這不是讀也不是寫). 另外, 幾個 ioctl 命令被核心識別而不必引用 fops 表. 如果裝置不提供 ioctl 方法, 對於任何未事先定義的請求(-ENOTTY, "裝置無這樣的 ioctl"), 系統調用返回一個錯誤.
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);//mmap 用來請求將裝置記憶體映射到進程的地址空間. 如果這個方法是 NULL, mmap 系統調用返回 -ENODEV.
int (*open) (struct inode *, struct file *);//儘管這常常是對裝置檔案進行的第一個操作, 不要求驅動聲明一個對應的方法. 如果這個項是 NULL, 裝置開啟一直成功, 但是你的驅動不會得到通知.
int (*flush) (struct file *, fl_owner_t id);//flush 操作在進程關閉它的裝置檔案描述符的拷貝時調用; 它應當執行(並且等待)裝置的任何未完成的操作. 這個必須不要和使用者查詢請求的 fsync 操作混淆了. 當前, flush 在很少驅動中使用; SCSI 磁帶驅動使用它, 例如, 為確保所有寫的資料在裝置關閉前寫到磁帶上. 如果 flush 為 NULL, 核心簡單地忽略使用者應用程式的請求.
int (*release) (struct inode *, struct file *);//在檔案結構被釋放時引用這個操作. 如同 open, release 可以為 NULL.
int (*fsync) (struct file *, loff_t, loff_t, int datasync);//這個方法是 fsync 系統調用的後端, 使用者調用來重新整理任何掛著的資料. 如果這個指標是 NULL, 系統調用返回 -EINVAL.
int (*aio_fsync) (struct kiocb *, int datasync);//這是 fsync 方法的非同步版本.
int (*fasync) (int, struct file *, int);//這個操作用來通知裝置它的 FASYNC 標誌的改變. 非同步通知是一個進階的主題, 在第 6 章中描述. 這個成員可以是NULL 如果驅動不支援非同步通知.
int (*lock) (struct file *, int, struct file_lock *);//lock 方法用來實現檔案加鎖; 加鎖對常規檔案是必不可少的特性, 但是裝置驅動幾乎從不實現它.
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);//sendpage 是 sendfile 的另一半; 它由核心調用來發送資料, 一次一頁, 到對應的檔案. 裝置驅動實際上不實現 sendpage.
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);//這個方法允許模組檢查傳遞給 fnctl(F_SETFL...) 調用的標誌.
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
 loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
};

ext4: file.c檔案裡

const struct file_operations ext4_file_operations = {
.llseek = ext4_llseek,
.read_iter = generic_file_read_iter,
.write_iter = ext4_file_write_iter,
.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ext4_compat_ioctl,
#endif
.mmap = ext4_file_mmap,
.open = ext4_file_open,
.release = ext4_release_file,
.fsync = ext4_sync_file,
.splice_read = generic_file_splice_read,
.splice_write = iter_file_splice_write,
.fallocate = ext4_fallocate,
};


static ssize_t ext4_file_write_iter(struct kiocb *iocb, struct iov_iter *from)->ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)

iocb->ki_flags & IOCB_DIRECT判斷是否進入直接IO

Direct IO是write函數的一個選項,用來確定資料內容直接寫到磁碟上,而非緩衝中,保證即使系統異常了,也能保證緊要資料寫到磁碟上,Direct IO流程也是接續著寫檔案流程而來的。核心走到__generic_file_aio_write函數時,系統根據file->f_flags & O_DIRECT判斷進入DirectIO處理的分支,依次先看generic_file_direct_write函數,主要有filemap_write_and_wait_range,invalidate_inode_pages2_range和mapping->a_ops->direct_IO起作用。

filemap_write_and_wait_range主要用來刷mapping下的髒頁,filemap_write_and_wait_range如果有寫入量則返回,後續的兩個函數則不執行。作用就是檢查當前記憶體中是否由對應將要direct_IO的快取頁面,如果有,則將其快取標籤為無效。目的是,因為direct_IO寫入的資料並不緩衝,如果direct_IO寫入資料之前有對應緩衝,而且是clean的,direct_IO完成之後,緩衝和磁碟資料就不一致了,讀取緩衝的時候,如果沒有保護,擷取的資料就不是磁碟上的資料。如果的確有對應快取標籤為無效,則返回不執行後面的函數。mapping->a_ops->direct_IO通過__blockdev_direct_IO實現,在direct_io_worker中組裝了dio結構,然後通過dio_bio_submit,本質就是通過submit_bio(dio->rw, bio)提交到io層。所謂direct_io和其他讀寫比較就是跨過了buffer層,不要中間線程pdflush和kjournald定期刷盤到IO層。這個時候也不一定資料就在磁碟上了,direct_IO就是先假定IO的裝置驅動沒有較大延時的。


generic_file_direct_write(iocb, from, iocb->ki_pos);->ssize_t generic_perform_write(struct file *file,struct iov_iter *i, loff_t pos)

->a_ops>write_begin(file, mapping, pos, bytes, flags,&page, &fsdata);

首先通過調用函數a_ops->write_begin為資料寫入分配頁緩衝並為這個page準備一組buffer_head結構用於描述組成這個page的資料區塊,在函數 iov_iter_copy_from_user_atomic中將使用者空間資料拷入該頁緩衝,然後調用函數a_ops->write_end將page中的每一個buffer_head標記為dirty。

 

file->f_mapping是從對應的inode->f_mapping而來,inode->f_mapping->a_ops是由對應的檔案系統類型在產生這個inode時賦予的。在ext3檔案系統中file->f_mapping->a_ops->write_begin和file->f_mapping->a_ops->write_end

inode.c裡

static const struct address_space_operations ext4_da_aops = {
.readpage = ext4_readpage,
.readpages = ext4_readpages,
.writepage = ext4_writepage,
.writepages = ext4_writepages,
.write_begin = ext4_da_write_begin,
.write_end = ext4_d

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.