这是新开的一个博客专题,将结合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;
}
复制代码