UNIX I/O
Everything is a file , in Unix
所有的IO设备(例如网络,磁盘和终端)都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行
UNIX I/O下输入输出统一的方式
1.打开文件
2.Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(fd=0),标准输出(fd=0),标准错误(fd=2)
2.改变当前文件的位置
3.读写文件
4.关闭文件
打开文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int open (char *filename,int flags,mode_t mode);
关闭文件
#include<unstd.h>
int close(int fd);
读和写文件
#include<unstd.h>
ssize_t read(int fd,void *filename,size_t n);
ssize_t write(int fd,const void *buf,size_t n);
读:从文件读到内存
写:从内存写到文件
读取文件数据
#include<unstd.h>
#include<sys/stat.h>
#include<sys/types.h>
int stat(const char *filename,struct stat *buf)
int fstat(int fd,struct stat *buf)
I/O重定向
#include<unistd.h>
int dup2(int oldfd,int newfd);
该函数的作用是用oldfd所指的文件内容去覆盖newfd所指的文件的内容。
若newfd已经打开了,dup2会在复制oldfd之前关闭newfd。功能如下图:
共享文件
描述符表: 每个进程有独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的文件描述符表项指向文件表中的一个表项
文件表: 打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。
v-node表: 所有进程共享这张v-node表,每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员
(以下图片来源深入理解计算机系统第三版)
无文件共享的情况:
有文件共享的情况:
父子进程共享文件的情况:
系统IO的举例
注意:以下代码中的打开关闭或者读写文件的函数(首字母大写的函数)不是系统定义的,而是在csapp.h头文件中自定义的包含出错处理的函数
abcde.txt的内容为:
abcde
练习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;
}
dup2(fd2,fd3)是用fd2所指文件的内容覆盖fd3所指文件的内容,所以当fd2被读取一个字符(文本的第一个字符)之后,到fd3读取一个字符读的就是fd2读取之后的下一个字符,即文本的第二个字符,而fd1与fd2,fd3无关,所以其读取的字符就是文本的第一个字符。
运行结果如下:
练习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;
}
sleep(time)函数来睡眠time秒,第一个sleep函数是为了让父进程先运行,第二次使用sleep是为了运行子进程,由于子进程继承的是父进程打开的文件,所以当父进程读取一个字符之后子进程读取的就是下一个字符
练习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;
}
/*abcde.txt
pqrswxyzef
*/
如何访问文件的一些参数:
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:可读可写
O_CREAT:如果文件不存在就创建它的一个空文件
O_TRUNC:如果文件已经存在则截断它,即覆盖原有文件的内容
O_APPEND:追加,即写在文件原有内容的结尾处
fd1,fd2,fd3都是对同一个文件进行操作
fd1文件写“pqrs”的时候是以截断的方式,所以覆盖了文本文件中原有的内容“abcde”—文件内容为:pqrs
fd3文件写“jklmn”的时候是以追加的方式,所以文件原有的内容不变,只增加了几个字符—文件内容为:pqrsjklmn
fd2重定向到fd1所指的文件表(fd2=dup(fd1)),对fd2进行操作实际是对fd1进行操作,fd2四个字符覆盖了fd3添加的内容的前4个字符—文件内容为:pqrswxyzn
fd3以追加的方式在文件末尾又添加了两个字符—文件内容为:pqrswxyznef
修改过后的abcde.txt
练习4
/* $begin statcheck */
#include "csapp.h"
```c
int main (int argc, char **argv)
{
struct stat stat;
char *type, *readok;
/* $end statcheck */
if (argc != 2) {
fprintf(stderr, "usage: %s <filename>\n", argv[0]);
exit(0);
}
/* $begin statcheck */
Stat(argv[1], &stat);
if (S_ISREG(stat.st_mode)) /* Determine file type */
type = "regular";
else if (S_ISDIR(stat.st_mode))
type = "directory";
else
type = "other";
if ((stat.st_mode & S_IRUSR)) /* Check read access */
readok = "yes";
else
readok = "no";
printf("type: %s, read: %s\n", type, readok);
exit(0);
}
该部分展示了如何使用宏和stat函数来读取和解释一个文件的st_mode位
Linux在sys/stat.h中定义了宏谓词来确定st_mode成员的文件类型,如下:
S_ISREG(m):是否是普通文件
S_ISDIR(m):是否是目录文件
S_ISSOCK(M):是否是网络套接字
计算机系统基础习题部分
foobar.txt的内容为:
foobar
练习题10.2
#include"csapp.h"
int main()
{
int fd1,fd2;
char c;
fd1=Open("foobar.txt",O_RDONLY,0);
fd2=Open("foobar.txt",O_RDONLY,0);
Read(fd1,&c,1);
Read(fd2,&c,1);
printf("c=%c\n",c);
exit(0);
}
fd1和fd2打开文件并没有关联,重新打开一个文件光标都在开头,所以二者读到的字符都是文件的第一个字符:f
练习题10.3
#include"csapp.h"
int main()
{
int fd;
char c;
fd=Open("foobar.txt",O_RDONLY,0);
if(Fork()==0){
Read(fd,&c,1);
exit(0);
}
Wait(NULL);
Read(fd,&c,1);
printf("c=%c\n",c);
exit(0);
}
父子进程共享文件,子进程读完之后光标移到下一个字符处,父进程再读读的就是第二个字符
练习题10.5
#include"csapp.h"
int main()
{
int fd1,fd2;
char c;
fd1=Open("foobar.txt",O_RDONLY,0);
fd2=Open("foobar.txt",O_RDONLY,0);
Read(fd2,&c,1);
Dup2(fd2,fd1);
Read(fd1,&c,1);
printf("c=%c\n",c);
exit(0);
}
首先读取了fd2的第一个字符,接着fd1被重定向到fd2,再读fd1实际读的就是fd2,所以最终读取的是fd2的第二个字符