unix編程以及xv6系統淺談(一)文件I/O

這是新開的一個博客專題,將結合unix的一些系統庫,系統調用等等,以及結合一個小的操做系統xv6的源碼來仔細的剖析一些系統調用的用法和其中的實現,因爲時間和篇幅的關係,沒有辦法做特別深刻的調研,在這裏只寫一些淺顯的東西,做拋磚引玉只用,若內容有誤但願你們多多評論以斧正,謝謝
複製代碼

1.文件I/O

1.1 基本api接口

​ 本章描述的都是不帶緩衝的I/O,即都是調用內核中的系統調用node

​ 對內核而言,全部打開的文件都經過文件描述符來引用(非負整數),變化範圍爲0-OPEN_MAX-1(通常63)c++

​ shell 中將文件描述符0表示爲輸入,1表示爲輸出,2爲標準錯誤shell

​ 同時可使用宏STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO來表示上述的文件api

#include<fcntl.h>
int open(const char* path,int oflag,.../*mode_t mode*/)
    僅當建立新文件時才使用最後這個參數
    返回的文件描述符必定時最小的未用描述符數值
    path:
		要打開或者建立的文件名
	oflag:
		說明此函數的多個選項,能夠用|來構成
		1.必須指定而且之能指定一個
				O_RDONLY	只讀
				O_WRONLY	只寫
				O_RDWR		讀寫打開
		2.
				O_APPEND		每次寫時都追加到文件的尾端,注意是每次,因爲該操做爲原子操做,這樣規避了多進程操做時的寫覆蓋之類的互斥問題!
				O_CREAT			若文件不存在則建立它,此時mode參數起做用,指定該文件的訪問權限位
				O_EXCL			若是同時指定了O_CREAT,文件已經存在,則出錯,能夠測試文件存在,
								不存在則建立,這是一個完整的原子操做
				O_TRUNC			若是此文件存在,並且爲可寫打開,則文件長度截斷爲0
int openat(int fd,const char* path,int oflag,.../*mode_t mode*/)
	fd參數的3種3可能性
		1.path爲絕對路徑名,則忽略參數fd
		2.path爲相對路徑名,則fd指出相對路徑名在系統中的開始地址,fd參數時經過打開相對路徑所在目錄獲取
		3.fd爲AT_FDCWD,則爲當前工做目錄
int creat(const char* path,mode_t mode)
	等效於open(path,O_WRONLY|O_CREAT|O_TRUNC,mode)
    注意以只寫的方式打開所建立的文件,而且從頭開始寫
int	close(int fd)
    成功則返回0,不然-1
  	關閉一個文件以及在上面的全部記錄鎖,注意,進程終止時內核會自動關閉它全部的打開文件
off_t lseek(int fd,offset_t offset,int whence)
    每一個文件都有本身的當前文件偏移量
    成功則返回新的文件偏移量,不然返回-1(用來判斷套接字等不能夠設置文件偏移量的文件)
    whence:
			SEEK_SET(0)	文件開始處
			SEEK_CUR(1)	當前文件偏移量所在位置
			SEEK_END(2)	文件結尾處
	offset:
			<0		向前移動
			>0		向後移動
ssize_t read(int fd,void* buf,size_t nbytes)
    返回讀到的字節,結尾則爲0,出錯爲-1
    從文件中讀取nbytes字節的內容放入buf中
    例:文件還剩30字節,要求讀100,則讀取30,返回30,並在下一次讀取返回0
ssize_t write(int fd,const void* buf,size_t nbytes)
    返回寫入的字節數,出錯返回-1
    通常狀況返回值=nbytes,不然可能磁盤寫滿or超過了一個給定進程的文件長度限制
複製代碼

​ unix系統支持在不一樣進程間共享打開文件,下面介紹這種共享的數據結構數據結構

​ (1)pcb中擁有的文件描述符表app

​ 文件描述符標誌異步

​ 指向一個文件表項的指針async

​ (2)內核維持的一張文件表函數

​ 文件狀態標誌(讀寫,添加等等)測試

​ 當前文件偏移量

​ 指向該文件V節點表項的指針

​ (3)v節點(Linux下面爲一個與文件系統無關的i節點)

​ 指向i節點的指針

​ 若兩個不一樣的進程同時用open打開同一個文件,二者擁有不一樣的vnode可是指向相同的inode

​ 若fork出子進程,則子進程各自的每個打開文件描述符共享同一個文件表項,即偏移量相同

ssize_t pread(int fd,void* buf,size_t nbytes,off_t offset)
    返回讀到的字節,文件尾爲0,出錯爲-1
    調用pread至關於調用lseek後調用read,可是:
    	調用pread沒法終端起定位操做
    	不更新當前文件偏移量
ssize_t pwrite(int fd,const void *buf,size_t nbytes,off_t offset)
    與上面的相似
複製代碼

​ 原子操做指的是由多步組成的一個操做,若是該操做原子的執行,則要麼執行完全部,要麼一步都不執行

//複製現有的文件描述符
int dup(int fd)
    返回當前可用文件描述符中的最小數值
int dup2(int fd,int fd2)
    用fd2指定新描述符的值,若是fd2已經打開,則將其先關閉,若是fd等於fd2,則返回fd2,而不關閉它
//注意,這些函數返回的新文件描述符與原有的共享一個文件表項,即擁有相同的文件偏移量
複製代碼

根據上面的文件操做的一些性質,來實現具體的shell中的重定位操做

case REDIR:	//輸入輸出重定位的狀況 
    rcmd = (struct redircmd*)cmd;
    close(rcmd->fd);	//關閉文件操做符,分別是<或者> 
    if(open(rcmd->file, rcmd->mode) < 0){	//再次打開一個文件將被分配爲最小未使用的即上面的fd 
      printf(2, "open %s failed\n", rcmd->file);
      exit();
    }
    runcmd(rcmd->cmd);	//遞歸的調用拆解後的命令(改過輸入輸出文件描述符後) 
    break;
複製代碼

同時也能夠藉助dup函數實現管道命令操做

case PIPE:	//管道|的實現 
    pcmd = (struct pipecmd*)cmd;
    if(pipe(p) < 0)
      panic("pipe");
    if(fork1() == 0){	//子進程1中
      close(1);		//關閉以掛載新的文件表項
      dup(p[1]);
      close(p[0]);	//關閉以防止read的阻塞等待
      close(p[1]);
      runcmd(pcmd->left);	//注意通常會調用exec,exec會替換調用它的進程的內存可是會保留它的文件描述符表
    }
    if(fork1() == 0){	//子進程2中
      close(0);
      dup(p[0]);
      close(p[0]);
      close(p[1]);
      runcmd(pcmd->right);
    }
    close(p[0]);
    close(p[1]);
    wait();
    wait();
    break;
複製代碼

當咱們向文件寫入數據的時候,內核一般先將數據複製到緩衝區中,而後排入隊列,晚些時候再寫入磁盤

void sync()
    將全部修改過的塊緩衝區排入寫隊列,而後就返回,並不等待實際寫磁盤操做結束
int fsync(int fd)
    只對由文件描述符fd指定的一個文件起做用,而且會等待磁盤操做結束
int fdatasync(int fd)
    隻影響文件的數據部分,並不會更新文件的屬性
int fcntl(int fd,int cmd,.../*int arg*/)
    返回值依賴於cmd,出錯則返回-1
    (1)複製一個已有的描述符(F_DUPFD)
    		複製fd進入>=arg的最小值,與其共享同一文件表項
    (2)獲取/設置文件描述符標誌(F_GETFD,F_SETFD)
    (3)獲取/設置文件狀態標誌(F_GETFL(arg通常設置0),F_SETFL)
    		getfl後須要與O_ACCMODE求&來獲取狀態的幾個狀態字
    			O_RDONLY	只讀打開
    			O_WRONLY	只寫打開
    			O_RDWR		讀寫打開
    			O_EXEC		只執行打開
    			O_SEARCH	只搜索打開目錄
    		列:(val&O_ACCMODE) == O_RDONLY
    		能夠直接與得到的狀態字求與判斷爲真的幾個狀態字
    			O_APPEND	追加寫
    			O_NONBLOCK	非阻塞模式
    			O_SYNC		等待寫完成(數據和屬性)
    		列:if(val&O_APPEND)
    	注意修改文件狀態標誌的時候,先獲取一下文件 狀態標誌
    			添加狀態:val|=flags;
				去除狀態:val&=~flags;
	(4)獲取/設置異步I/O全部權(F_GETDOWN,F_SETOWN)
	(5)獲取/設置記錄鎖(F_GETLK,F_SETLK,F_SETLKW)
複製代碼

1.2 詳細的實現原理

​ 參照xv6的內核源碼,open中的實現是這樣的

int sys_open(void) {
  char *path;
  int fd, omode;
  struct file *f;
  struct inode *ip;
    //若輸入的參數有問題則返回-1
  if(argstr(0, &path) < 0 || argint(1, &omode) < 0)
    return -1;
  begin_op();
    //若是設置了O_CREAT關鍵字則建立文件而且返回其inode
  if(omode & O_CREATE){
    ip = create(path, T_FILE, 0, 0);
    if(ip == 0){
      end_op();
      return -1;
    }
  } else {
      //不然若是輸入的文件名沒有找到則返回錯誤,不然返回找到的inode節點
    if((ip = namei(path)) == 0){
      end_op();
      return -1;
    }
    ilock(ip);
      //若是是目錄文件則不能夠進行除了讀取以外的操做
    if(ip->type == T_DIR && omode != O_RDONLY){
      iunlockput(ip);
      end_op();
      return -1;
    }
  }
//經過filealloc()函數來向內核進程申請一個文件表項,fdalloc()函數來從pcb中得到最小的違背使用的文件描述符
  if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){
    if(f)
      fileclose(f);
    iunlockput(ip);
    end_op();
    return -1;
  }
  iunlock(ip);
  end_op();
//設置文件表項,主要是設置文件類型爲目錄或者普通文件,因爲管道是pipe建立因此不會出現open狀況
    //同時設置文件中的偏移量,這裏xv6中沒有append關鍵字因此統一的設置爲0
  f->type = FD_INODE;
  f->ip = ip;
  f->off = 0;
  f->readable = !(omode & O_WRONLY);
  f->writable = (omode & O_WRONLY) || (omode & O_RDWR);
  return fd;
}
複製代碼

​ 上述代碼中的creat函數在xv6中的實現以下

static struct inode* create(char *path, short type, short major, short minor) {
  struct inode *ip, *dp;
  char name[DIRSIZ];
//獲取上級目錄的inode
  if((dp = nameiparent(path, name)) == 0)
    return 0;
  ilock(dp);
//檢查同名文件是否存在
  if((ip = dirlookup(dp, name, 0)) != 0){
    iunlockput(dp);
    ilock(ip);
      //文件名存在而且creat爲open調用則成功返回
    if(type == T_FILE && ip->type == T_FILE)
      return ip;
      //其餘狀況則會返回錯誤
    iunlockput(ip);
    return 0;
  }
//文件名不存在則會申請一個inode節點
  if((ip = ialloc(dp->dev, type)) == 0)
    panic("create: ialloc");

  ilock(ip);
  ip->major = major;
  ip->minor = minor;
  ip->nlink = 1;
  iupdate(ip);
//若是是目錄則初始化.和..
  if(type == T_DIR){  // Create . and .. entries.
    dp->nlink++;  // for ".."
    iupdate(dp);
    // No ip->nlink++ for ".": avoid cyclic ref count.
    if(dirlink(ip, ".", ip->inum) < 0 || dirlink(ip, "..", dp->inum) < 0)
      panic("create dots");
  }
//
  if(dirlink(dp, name, ip->inum) < 0)
    panic("create: dirlink");

  iunlockput(dp);
//返回其inode
  return ip;
}
複製代碼

​ 至於close操做則仍是較爲簡單的

int sys_close(void) {
  int fd;
  struct file *f;
 //判斷是否存在這個文件描述符
  if(argfd(0, &fd, &f) < 0)
    return -1;
    //關閉pcb中的對應的文件描述符
  myproc()->ofile[fd] = 0;
  fileclose(f);
  return 0;
}
//調用的fileclose函數的源碼
void fileclose(struct file *f) {
  struct file ff;

  acquire(&ftable.lock);
  if(f->ref < 1)
    panic("fileclose");
    //減小文件計數,若該文件的計數爲0則關閉這個文件
  if(--f->ref > 0){
    release(&ftable.lock);
    return;
  }
  ff = *f;
  f->ref = 0;
  f->type = FD_NONE;
  release(&ftable.lock);
//關閉文件
  if(ff.type == FD_PIPE)
    pipeclose(ff.pipe, ff.writable);
  else if(ff.type == FD_INODE){
    begin_op();
    iput(ff.ip);
    end_op();
  }
}
複製代碼

​ 下面介紹read和write的實現方式

int sys_read(void) {
  struct file *f;
  int n;
  char *p;
//同上
  if(argfd(0, 0, &f) < 0 || argint(2, &n) < 0 || argptr(1, &p, n) < 0)
    return -1;
  return fileread(f, p, n);
}
int fileread(struct file *f, char *addr, int n) {
  int r;
//判斷是否能夠讀
  if(f->readable == 0)
    return -1;
    //管道文件則使用管道的讀取方法
  if(f->type == FD_PIPE)
    return piperead(f->pipe, addr, n);
    //讀取文件信息
  if(f->type == FD_INODE){
    ilock(f->ip);
      //讀取到相關字節後則將文件偏移量則加上相關的讀取字節數
    if((r = readi(f->ip, addr, f->off, n)) > 0)
      f->off += r;
    iunlock(f->ip);
    return r;
  }
  panic("fileread");
}
int sys_write(void) {
  struct file *f;
  int n;
  char *p;
//同上
  if(argfd(0, 0, &f) < 0 || argint(2, &n) < 0 || argptr(1, &p, n) < 0)
    return -1;
  return filewrite(f, p, n);
}
int filewrite(struct file *f, char *addr, int n) {
  int r;

  if(f->writable == 0)
    return -1;
  if(f->type == FD_PIPE)
    return pipewrite(f->pipe, addr, n);
  if(f->type == FD_INODE){
    // write a few blocks at a time to avoid exceeding
    // the maximum log transaction size, including
    // i-node, indirect block, allocation blocks,
    // and 2 blocks of slop for non-aligned writes.
    // this really belongs lower down, since writei()
    // might be writing a device like the console.
    int max = ((MAXOPBLOCKS-1-1-2) / 2) * 512;
    int i = 0;
    while(i < n){
      int n1 = n - i;
      if(n1 > max)
        n1 = max;

      begin_op();
      ilock(f->ip);
       //寫入後更新文件偏移量
      if ((r = writei(f->ip, addr + i, f->off, n1)) > 0)
        f->off += r;
      iunlock(f->ip);
      end_op();

      if(r < 0)
        break;
      if(r != n1)
        panic("short filewrite");
      i += r;
    }
    return i == n ? n : -1;
  }
  panic("filewrite");
}
複製代碼

​ dup的原理不一樣於open,是不會申請一個新的文件表項的

int sys_dup(void) {
  struct file *f;
  int fd;

  if(argfd(0, 0, &f) < 0)
    return -1;
    //從pcb中的文件描述符表中申請
  if((fd=fdalloc(f)) < 0)
    return -1;
    //增長文件引用計數
  filedup(f);
  return fd;
}
複製代碼
相關文章
相關標籤/搜索