系统级I/O
我们应该要清楚的很重要的一句话:Linux中一切皆文件
Linux文件类型
- 普通文件
- 目录文件
- 套接字文件
输入输出统一且一致的执行方式
- 打开文件
- Linux shell 创建的每个进程开始时都有三个打开 的文件:标准输入(描述符为0)、标准输出(1)、标准错误(2)
- 改变当前的文件位置
- 读写文件
- 关闭文件
内核用三个相关的数据结构来表示打开的文件
- 进程表:内核维护一张进程表,记录每一个进程项,进程项中维持了一张在该进程中所有的打开的文件描述符,每个文件描述符记录了问价描述符标志和文件表项。
- 文件表:内核维护一张文件表,用来记录所有的打开的文件表项。每个文件表项记录文件描述符状态标记、文件指针当前偏移量以及指向文件v节点所在地址的指针。
- v节点表:同文件表一样,所有进程共享这张v节点表,每个表项包含stat结构中的大多数信息
文件共享
没有文件共享
open函数
进程是通过调用open函数来打开一个已经存在的文件或者创建一个新文件的
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(char *filename, int flags, mode_t mode);
open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符
flags参数
- O_RDONLY:只读
- O_WRONLY:只写
- O_RDWY:可读可写
mode参数
指定了新文件的访问权限,根据不同权限信息&、|操作赋予文件不同的权限
访问权限:可读(二进制100)、可写(010)、可执行(001)
实验
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio>
int main ()
{
int fd;
fd=open("a.txt",O_RDONLY,0);
printf("fd=%d\n",fd);
return 0;
}
结果
fd=3
标准输入0,标准输出1,错误2,现在最小的文件描述符为3,所以输出3
close函数
手动关闭打开的进程描述符,成功返回0,否则返回-1
int close(int fd);
read函数
读文件
ssize_t read(int fd,void *buf,size_t n);
write函数
写文件
ssize_t write(int fd,const void *buf,size_t n);
I/O重定向 dup2函数
复制描述符表项oldfd到描述符表表项newfd,覆盖描述符表表项newfd以前 的内容,如果newfd已经打开了,dup2会在复制oldfd之前关闭newfd
#include <unistd.h>
int dup2 (int oldfd,int newfd);
实验
#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;
}
结果
fd2替换掉了fd3,对fd3的操作实际上是对fd2的操作
#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;
}
结果
由于父子进程共享的是一个文件,子进程读取之后光标移向下一位置,父进程进来读,因此父进程中c2=c