Linux文件简介
Linux中一切皆文件
文件打开时返回一个文件描述符,此后对文件的所有操作都标识这个文件,程序只需记住这个描述符。
Linux shell创建进程时都会打开三个文件:标准输入0、标准输出1和标准错误2。
文件类型有普通文件、目录文件和套接字文件。
今天主要是学习Linux的文件相关的系统I/O函数。
在学习这些函数之前,需要先了解Linux系统的进程间的文件共享和管理的问题。系统内核为了维持进程与文件之间的关系,定义了三个结构体。
1、进程表:内核维护一张进程表,记录每一个进程项,进程项中维持了一张在该进程中所有的打开的文件描述符,每个文件描述符记录了问价描述符标志和文件表项。
2、文件表:内核维护一张文件表,用来记录所有的打开的文件表项。每个文件表项记录文件描述符状态标记、文件指针当前偏移量以及指向文件v节点所在地址的指针。
3、v节点表:内核位置一张v节点表,记录所有打开的v节点,v节包含v节点信息以及i节点信息,每一个打开的文件有一个v节点结构,v节点中包含的i节点(索引节点)记录了文件的详细信息,例如记录了文件的长度,在盘中的地址等具体信息。
进程之间对文件的管理就是通过这个三张表的关系层层连接。一个进程打开一个未打开的文件时候,就会生成产生文件描述符、文件表项一v节点结构,来记录这个文件的详细信息,如若另外一个进程打开与该进程相同的文件时候,则在另外一个进程中只会生成一个当前进程下的文件描述符和一个文件表项,但是是不会在生成v节点结构,一个打开的文件有且只有一个v节点结构,也就是两个进程的文件表项就会共用该v节点。最后给出一张图,帮助理解。如下图所示(打开文件的内核将数据结构):
open函数
进程是通过调用open来打开一个已经存在的文件,或者创建一个不存在的文件的。
函数原型
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(char *filename, int flags, mode_t mode);
参数解析
filename:一个文件路径,可以是相对路径,也可以是绝对路径
flags:它有两个可选信息
主要信息:
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:可读可写
次要信息:
O_CREAT:如果文件不存在,就创建它的一个截断的空文件
O_TRUNC:如果文件存在就阶段他
O_APPEND:每次写操作,都在文件结尾处添加
mode参数
主要是一些用户访问权限信息,在Linux中,我们文件权限可以看成八进制的标志位比如对于可读,可写,可执行 分别对应于二进制的100,010,001在比如可读可写对应于110(十进制6),可读可写可执行(111十进制7)而对于用户而言,又分为拥有者用户,同一用户组的用户,和所有用户,共同构成一个文件的访问权限信息比如777 就是对拥有者,同一组用户,所有用户打开所有权限。而mode参数,就是根据不同的权限信息的“&”和“|”操作,来赋予要给文件不同权限
如下图所示
代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(){
int fd1,fd2;
fd1 = open("a.txt", O_RDONLY, 0);
fd2 = open("b.txt", O_RDONLY, 0);
printf("fd2=%d\n",fd2);return 0;
}
结果:
fd2=4
分析:
之前的文件描述符又标准输入0,标准输出1,和错误2
然后fd1打开为3,所以fd2打开后为4
close函数
虽然说进程结束后,会自动关闭所有打开的文件描述符,但是还是希望程序员自己手动关闭。函数成功返回0,失败返回-1。
int close(int fd);
//@param fd 需要关闭文件的文件描述符
read和write函数
ssize_t read(int fd,void *buf,size_t n);
/*从描述符为 fd 的当前文件位置复制最多 n 个字节 到 内存位置 buf 返回:若成功则为读的字节数,若为EOF则为0,若出错为-1 */
ssize_t write(int fd,const void *buf,size_t n);
/* 从内存位置 buf 复制最多n个字节 到描述符为 fd 的当前文件位置。返回:若成功则为写的字节数,若出错为-1*/
/* $begin cpstdin */
#include "csapp.h"
int main(void)
{
char c;
while(Read(STDIN_FILENO, &c, 1) != 0)
Write(STDOUT_FILENO, &c, 1);
exit(0);
}
结果:
将输入的内容输出,即将输入文件的内容实时写到输出文件。
dup2函数
int dup2(int oldfd, int newfd);
/*
这个函数就是复制oldfd到newfd
也就是说在进程的文件描述符表中,newfd的内容将会被oldfd替代![在这里插入图片描述](https://img-blog.csdnimg.cn/20191201211443278.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mjc1NTAxNQ==,size_16,color_FFFFFF,t_70)
*/
使用dup2之后
代码:
#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;
}
fname的文件是abcde.txt里面包含abcde五字符。
结果:
c1 = a, c2 = a, c3 = b
分析:
dup2用fd2将fd3替代掉,即fd3操作的是fd2 的文件表
代码:(考试题)
#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;
}
结果:
pqrswxyznef
分析:
fd1打开后,输入pqrs,fd3后打开并append紧随其后,写下了jklmn,即变成了pqrsjklmn,
后使用fd2,fd2 dup fd1,所以使用fd1的光标,写下了wxyz,即pqeswxyzn,
后fd3无视fd2写下了ef跟在n之后,变成了pqrswxyznef。