這是新開的一個博客專題,將結合unix的一些系統庫,系統調用等等,以及結合一個小的操做系統xv6的源碼來仔細的剖析一些系統調用的用法和其中的實現,因爲時間和篇幅的關係,沒有辦法做特別深刻的調研,在這裏只寫一些淺顯的東西,做拋磚引玉只用,若內容有誤但願你們多多評論以斧正,謝謝
複製代碼
本章描述的都是不帶緩衝的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)
複製代碼
參照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;
}
複製代碼