csapp之系统级i/o

前段时间学完csapp的系统级i/o,在这里做一点笔记
一、UNIX I/O
在UNIX系统中有一个说法,一切皆文件。所有的I/O设备,如网络、磁盘都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行。这种将设备映射为文件的方式,允许UNIX内核引出一个简单、低级的应用接口,称为UNIX I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。

1.打开文件
内核返回一个小的非负整数,叫做描述符
等于内核分配一个文件名,来标示当前的文件。
内核记录有关这个打开文件的所有信息。应用程序只需要记住标示符。
Unix外壳创建进程时都有三个打开的文件

标准输入(标示符0)
标准输出(标示符1)
标准错误(标示符2)
头文件<unistd.h>定义了常量代替显式的描述符值
STDIN_FILENO
STDOUT_FILENO
STDERR_FILENO

2.改变当前的文件位置
文件位置是从文件开头起始的字节偏移量
3.读写文件
4.关闭文件

无论进程以何种原因终止,内核都会关闭所有打开的文件并释放它们的存储器资源。

二、打开和关闭文件
1.进程是通过调用 open函数来打开一个已存在的文件或者创建一个新文件的

int open(char *filename,int flags,mode_t mode);
         //返回:若成功则为新文件描述符,若出错为-1

open函数将filename转换为一个文件描述符,并且返回描述符数字。

  • 返回的描述符总是在进程当前没有打开的最小描述符。
  • flags参数指明了进程打算如何访问这个文件:
  • 可是以一个多个掩码的或。

O_RDONLY: 只读
O_WRONLY: 只写
O_CREAT : 如果文件不存在,就创建一个截断的(truncated)(空)文件。
O_TRUNC : 如果文件已存在,就截断它(长度被截为0,属性不变)
O_APPEND: 在每次写操作前,设置文件位置到文件的结尾
O_RDWR: 可读可写

  • mode参数指定了新文件的访问权限位。
  1. close函数关闭一个打开的文件
int close(int fd);
   //返回: 若成功则为0,若出错则为-1

三、读和写文件
通过调用readwrite函数来完成输入和输出

#include <unistd.h>

ssize_t read(int fd,void *buf,size_t n);
//read函数从描述符fd的当前文件位置拷贝最多n个字节到存储器buf

                    返回:若成功则为读的字节数,若EOF则为0,若出错为 -1.
ssize_t write(int fd,const void *buf,size_t n)
//write函数从存储器位置buf拷贝至多n个字节到描述符fd的当前文件位置

                    返回:若成功则为写的字节数,若出错则为-1

四、读取文件元数据
应用程序能够通过调用statfstat函数检索到关于文件的信息(有时也称为文件的元数据)

#include <unistd.h>
#include <sys/stat.h>

int stat(const char *filename, struct stat *buf);
int fstat(int fd, struct stat *buf);
                  返回:若成功则为0,出错则为-1

区别是stat函数以文件名作为输入,而fstat以文件描述符作为输入.
挂一张stat的数据结构
在这里插入图片描述
五、共享文件
内核用相关的数据结构来表示打开的文件:

描述符表: 每个进程都有它独立述符表,每个打开的描述符表项指向文件表中的一个表项。

文件表: 打开文件的集合由一张文件表来表示,所有的进程共享这张表。每个文件表的表项包括当前的文件位置,引用计数(即当前指向该表项的描述符表项数),以及一个指向v-node表中对应表项的指针。关闭一个文件描述符会减少相应的文件表表项中的引用计数。内核不会删除这个文件表表项,直到它的引用计数为0.

v-node表: 所有进程共享v-node表。每个表项包含相应打开文件的属性信息,包括stat结构的的大部分信息,诸如st_mode和st_size成员.

下图是典型的打开文件的内核数据结构,描述符1和4通过不同的打开文件表表项来引用两个不同的文件。没有共享文件。
图一
在这里插入图片描述
多个描述符也可以通过不同的文件表表项来引用同一个文件(共享),如下图所示。例如,如果以同一个filename调用open函数两次,就会发生这种情况。关键地方是每个描述符都有它自己的文件位置,所以对不同描述符的读操作可以从文件的不同位置获取数据.

图二在这里插入图片描述
对于fork的情况: 假设fork前,父进程有图一所示的打开文件。图三展示了调用fork之后的情况。子进程有一个父进程描述符的副本。父子进程共享相同的打开文件表几个,因此共享相同的文件位置。因而,在内核删除相应文件表表项之前,父子进程必须都关闭了它们的描述符.
图三
在这里插入图片描述
六、I/O重定向
Unix I/O提供dup2函数支持重定向

int dup2(int oldfd, int newfd);
        返回: 若成功则为非负的描述符,出差则为-1

dup2拷贝描述符表表项oldfd到描述符表表项newfd,覆盖描述符表表项newfd以前的内容。若newfd已经打开,dup2会在拷贝oldfd之前关闭newfd.

调用dup2(4,1)之前,初始状态如图一所示,调用之后如下图四所示.
在这里插入图片描述
接下来看几个相关题目
例题1

#include "csapp.h"

int main(int argc, char *argv[])
{
    int fd1, fd2, fd3;
    char c1, c2, c3;
    char *fname = argv[1];
    fd1 = open(fname, O_RDONLY, 0);
    fd2 = open(fname, O_RDONLY, 0);
    fd3 = open(fname, O_RDONLY, 0);
    dup2(fd2, fd3);

    read(fd1, &c1, 1);
    read(fd2, &c2, 1);
    read(fd3, &c3, 1);
    printf("c1 = %c, c2 = %c, c3 = %c\n", c1, c2, c3);

    close(fd1);
    close(fd2);
    close(fd3);
    return 0;
}

输出结果:c1=a,c2=a,c3=b

解析:在此题目中,先调用open函数打开同一个文件3次,此时f1、f2、f3的值应该分别为3、4、5,然后执行 dup2(fd2, fd3)重定向指令后,f3覆盖f2,此时对fd3的操作实际是对fd2的操作,每次打开文件时,光标都位于文件的头部,当第一次调用read时直接读出该文件的第一个字母 a ,第二次 调用read时,读出的字母也为’a’,当第三次调用read时,实际上是对fd2进行操作,此时光标处于’a’位置,即他读取的是’a’的下一个字母,因此读取字母为’b’,故输出为
c1=a,c2=a,c3=b

例题2

include "csapp.h"

int main(int argc, char *argv[])
{
    int fd1;
    int s = getpid() & 0x1;
    char c1, c2;
    char *fname = argv[1];
    fd1 = open(fname, O_RDONLY, 0);
    read(fd1, &c1, 1);
    if (fork()) {
	/* Parent */
	sleep(s);
	read(fd1, &c2, 1);
	printf("Parent: c1 = %c, c2 = %c\n", c1, c2);
    } else {
	/* Child */
	sleep(1-s);
	read(fd1, &c2, 1);
	printf("Child: c1 = %c, c2 = %c\n", c1, c2);
    }
    return 0;
}

输出结果:
Parent:c1=a,c2=b
Child: c1=a,c2=c
解析:首先调用getpid获取该进程的PID号,然后将与)0X01进行与运算,如果该进程pid的最后一位是0,则s=0,子进程将进入睡眠状态;若为1,则s=1,父进程将进入睡眠状态,由运行结果知子进程进入了睡眠,一开始调用read读取了abcde.txt文件中的第一个字母’a’,然后fork函数创建子进程,子进程也读取了字母’a’,然后子进程进入了睡眠状态,父进程继续读取文件的下一个字母,即’b’,因此父进程输出的是c1=a,c2=b,当子进程休眠过后,继续读取下一个字母,此时打开的仍是同一个文件,光标位于字母’b’后,即读取’b’的下一个字母’c’,故子进程输出c1=a,c2=c。
例题3
该文件最终内容是什么?

#include "csapp.h"

int main(int argc, char *argv[])
{
    int fd1, fd2, fd3;
    char *fname = argv[1];
    fd1 = open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
    write(fd1, "pqrs", 4);	

    fd3 = open(fname, O_APPEND|O_WRONLY, 0);
    write(fd3, "jklmn", 5);
    fd2 = dup(fd1);  /* Allocates new descriptor */
    write(fd2, "wxyz", 4);
    write(fd3, "ef", 2);

    close(fd1);
    close(fd2);
    close(fd3);
    return 0;
}

解析:该题中首先调用open函数创建了一个新的文件,接下来调用write函数向文件中写入四个字节’pqrs‘;
然后根据O_APPEND|O_WRONLY可知接下来调用的write是在文件的结尾加入5个字节’jklmn‘,由于都是对同一文件操作此时文件内容为’pqrsjklmn‘;
继续执行了 fd2 = dup(fd1)指令,又调用write函数,此时光标落在文件第四个字母’r‘后面,接下来对文件进行4个字节的覆盖,先对‘jklm’进行覆盖,然后在’n’前加上’wxyz’四个字节,此时文件中的内容为’pqrswxyzn’;
最后调用write在文件的结尾加上’ef’两个字节,故最后文件的内容为’pqrswxyznef’。

如有不正确之处,望各位批评指正,感谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值