read操作是任何作業系統裡的基本操作,我們來看一下在linux核心裡,read檔案是怎樣實作的。
read函數在使用者空間是由read系統調用實作的,由編譯器編譯成軟中斷int 0x80來進入核心空間,然後在中端門上進入函數sys_read,進而進入核心空間執行read操作。
sys_read函數定義在fs/read_write.c檔案,定義如下
asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
{
struct file *file;/*檔案指針*/
ssize_t ret = -EBADF;
int fput_needed;
/*輕量級的由檔案描述符得到檔案指針函數*/
file = fget_light(fd, &fput_needed);
if (file) {
/*file結構體裡的訓示檔案讀寫位置的int變量讀取*/
loff_t pos = file_pos_read(file);
/*vfs虛拟檔案系統實作read操作的地方*/
ret = vfs_read(file, buf, count, &pos);
/*file結構體裡的訓示檔案讀寫位置的int變量寫入*/
file_pos_write(file, pos);
/*釋放file結構體指針*/
fput_light(file, fput_needed);
}
return ret;
}
首先看看file_pos_read和file_pos_write函數吧,定義如下
static inline loff_t file_pos_read(struct file *file)
{
return file->f_pos;
}
static inline void file_pos_write(struct file *file, loff_t pos)
{
file->f_pos = pos;
}
定義很簡單,讀取的時候就是讀出file結構體的f_pos,寫入的時候就是寫到對應變量。訓示檔案的讀寫位置的變量就是在file結構體裡。
然後看一下fget_light和fput_light函數,定義如下
struct file fastcall *fget_light(unsigned int fd, int *fput_needed)
{
struct file *file;
/*得到目前程序的task_struct的打開的files指針*/
struct files_struct *files = current->files;
*fput_needed = 0;
/*如果隻有一個程序使用這個結構體,就不必考慮鎖,否則要先得到鎖才可以讀取*/
if (likely((atomic_read(&files->count) == 1))) {
/*從files結構體的fd數組上得到file結構體*/
file = fcheck_files(files, fd);
} else {
/*先上鎖,在得到對應結構體*/
rcu_read_lock();
file = fcheck_files(files, fd);
if (file) {
if (atomic_inc_not_zero(&file->f_count))
*fput_needed = 1;
else
/* Didn't get the reference, someone's freed */
file = NULL;
}
rcu_read_unlock();
}
return file;
}
static inline void fput_light(struct file *file, int fput_needed)
{ /*釋放并減少使用計數*/
if (unlikely(fput_needed))
fput(file);
}
然後傳回來看我們最重要的vfs_read函數,vfs_read函數定義在fs/read_write.c,定義如下
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
/*首先檢查檔案是否可以讀取,否則傳回壞的檔案描述符标記*/
if (!(file->f_mode & FMODE_READ))
return -EBADF;
/*如果沒有對應的檔案操作函數集合,也傳回錯誤*/
if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
return -EINVAL;
/*檢查有沒有權限*/
if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
return -EFAULT;
/*檢查目前寫入的地方有沒有被上鎖,是否可讀寫*/
ret = rw_verify_area(READ, file, pos, count);
if (ret >= 0) {
count = ret;
/*安全操作*/
ret = security_file_permission (file, MAY_READ);
if (!ret) {
/*如果file結構體裡有read函數,就調用*/
if (file->f_op->read)
ret = file->f_op->read(file, buf, count, pos);
else
/*否則就調用異步讀取的*/
ret = do_sync_read(file, buf, count, pos);
if (ret > 0) {
/*成功讀取以後,通知父目錄已經讀取,并在目前程序結構體上記錄*/
fsnotify_access(file->f_path.dentry);
add_rchar(current, ret);
}
inc_syscr(current);
}
}
return ret;
}
然後我們在進入do_sync_read函數看一看異步讀取是怎麼實作的,do_sync_read函數定義在fs/read_write.c,定義如下
ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
struct iovec iov = { .iov_base = buf, .iov_len = len };
struct kiocb kiocb;
ssize_t ret;
/*初始化讀寫控制塊*/
init_sync_kiocb(&kiocb, filp);
kiocb.ki_pos = *ppos;
kiocb.ki_left = len;
/*調用file_operation結構體的異步讀取函數*/
for (;;) {
ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos);
if (ret != -EIOCBRETRY)
break;
wait_on_retry_sync_kiocb(&kiocb);
}
/*如果沒結束,就等待*/
if (-EIOCBQUEUED == ret)
ret = wait_on_sync_kiocb(&kiocb);
*ppos = kiocb.ki_pos;
return ret;
}
至此,linux核心的read操作就算ok了,linux核心的sys_write和read很相似哦,隻要弄明白read,write也一定是可以搞明白的。