先介绍存取已存在的文件的系统调用,如open, read, write, lseek, close
然后介绍创建新文件的系统调用,如creat, mkmod
然后管理索引节点和文件系统的系统调用:chdir, chroot, chown, chmod, stat, fstat
更高级的系统调用:pipe, dup
系统调用mount, umount扩充了对用户可见的文件系统树
link, unlink修改文件系统层次的结构
本章还给出文件系统的抽象表示,是能兼容各种文件系统的关键;
重新强调三个数据结构:
系统调用open的语法格式是:fd=open(pathname, flags, modes)
参数含义:pathname表示文件路径;flags表示打开的类型(读或写);modes给出文件的许可权(如果文件正在被建立);
为什么open是系统调用啊,系统调用到底是什么样的概念?open不是一个函数嘛???
这里面需要强调的是:文件表是全局的,用户文件描述符表是进程的; 其中文件表中保存了偏移量,即每次读从哪里开始,写从哪里开始等;
假设第二个进程执行下列代码:
fd1=open("/etc/passwd", O_RDONLY);
fd2=open(“private”, O_RDONLY);
得到如下结果:
注意点:每个open调用都导致在用户描述符表和核心文件表中分配一个唯一表项,但在核心的内容索引节点表中,对每个文件只有一个表项;
这本书里很好的解释了一些设计,比如既然用户文件描述表项和文件表表项一一对应,那么为什么还需要文件表呢?不用文件表,直接让用户文件表项和索引节点表项相互关联不就完事了嘛?
上图中还展示了三项比较特殊的文件描述符,即0,1,2它们分别是标准输入、标准输出、错误输出,这个规定有助于帮助程序之间通过pipe来进行通讯;
系统调用read的语法格式是:number=read(fd, buffer, count);
算法还是有点复杂的,看下算法描述叭:
步骤很多,不过核心思想还是很简单的:
算法描述中,多次提到了u区中的一些参数:
#include
main()
{
int fd;
char buff[20],bigbuff[1024];
fd=open("/etc/passwd",O_RDONLY);
read(fd, buff, 20);
read(fd, bigbuf, 1024);
read(fd, buff, 20);
}
先看第一个read:
再看第二个read:
第二个read和第一个read其实没有区别,第二个read涉及到读第二个磁盘;
第三个read省略;
从上面的例子说明,从文件系统块的边界开始、而且大小为块的整数倍的IO操作具有很多优点;这样能使核心避免额外地重复算法read的循环;循环会带来哪些效率的损耗呢?
关于breada,首先我们看下breada在Linux0.11中的定义,然后再讨论如何使用它:
struct buffer_head *breada(int dev, int first, ...) {
va_list args;
struct buffer_head *bh, *tmp;
va_start(args, first);
if (!(bh = getblk(dev, first))) panic("bread: getblk returned NULL\n");
if (!bh->b_uptodate) ll_rw_block(READ, bh);
while ((first = va_arg(args, int)) >= 0) {
tmp = getblk(dev, first);
if (tmp) {
if (!tmp->b_uptodate) ll_rw_block(READA, bh);
tmp->b_count--;
}
}
va_end(args);
wait_on_buffer(bh);
if (bh->b_uptodate) return bh;
brelse(bh);
return (NULL);
}
代码细节可以不用管,我们只看函数参数,是变长参数写法,所以可以预读很多磁盘块,下面我们讨论这里关于breada的应用:
讨论下有关锁的问题:
看下面代码:
该代码和只open一次,只使用一个fd1来read有什么区别呢?