open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
- 参数解析
1.pathname:要打开或创建的目标文件
2.flags:打开文件时可以传入多个参数进程,用下面的一个或者多个常量进行“或”运算,构成flags.
参数:
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写打开
O_CRWAT 文件不存在创建它
O_APPEND 追加写
- 返回值: 成功返回新打开文件的描述符
失败返回 -1 - open函数具体使用哪个由具体应用场景相关,如目标文件不存在,需要open创建,则选择三个参数的open,它的第三个参数表示创建文件的默认权限,否则使用两个参数的open。
close
#include <unistd.h>
int close(int fd);
- 参数fd为要关闭文件的文件描述符
- 返回值:成功返回 0,失败返回-1
write
#include <unistd.h>
ssize_t pwrite(int fildes, const void *buf, size_t nbyte,
off_t offset);
ssize_t write(int fildes, const void *buf, size_t nbyte);
- 使用
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd = open("myfile",O_WRONLY | O_CREAT, 0644);
if(fd < 0)
{
perror("open");
return 1;
}
int count = 3;
const char* buf = "hello taotao\n";
while(count--)
{
write(fd,buf,strlen(buf));
}
close(fd);
return 0;
}
上述代码使用open打开并创建一个新文件并在新文件中写入3行hello taotao。
read
#include <unistd.h>
ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset);
ssize_t read(int fildes, void *buf, size_t nbyte);
- 使用
#include<string.h>
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
int main()
{
int fd = open("myfile",O_RDONLY);
if(fd < 0)
{
perror("open");
exit(1);
}
const char* msg = "hello taotao\n";
char buf[1024];
while(1)
{
ssize_t s = read(fd,buf,strlen(msg));
if(s > 0)
{
printf("%s",buf);
}else{
buf[s] = 0;
break;
}
}
close(fd);
}
文件描述符
前面我们在open提到了它的返回值:文件描述符,文件描述符号就是一个整数
- Linux 进程默认情况下会有3个缺省打开的文件描述符号,分别是标准输入0,标准输出1,标准错误2.
- 0, 1,2对应的物理设备一般是:键盘,显示器,显示器。
- 本质
当我们打开文件时,OS在内存中要创建相应的数据结构来描述目标文件,于是就有了file结构体,表示一个已经打开的文件对象,而进程执行open系统调用,所以必须让进程与文件关联起来,每个进程都有一个指针*files,指向一张表files_struct,该表最重要的部分就是包含了一个指针数组,每个元素都是一个指向打开文件的指针!,所以本质上,文件描述符就是该数组的下标。 - 文件描述符的分配单元
在files_struct数组中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
重定向
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
close(1);
int fd =open("myfile",O_RDONLY | O_CREAT,00644);
if(fd < 0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
fflush(stdout);
close(fd);
exit(0);
}
执行上述代码我们发现本应该输出在显示器上的内容,输出到了文件myfile中,其中fd为1,这种现象叫做重定向,常见的重定向有>,>>,<。
重定向的本质:
printf是c库当中的IO函数,一般在stout中输出,但是stdout底层访问文件的时候,找的还是fd:1,但此时,fd:1下标表示的内容已经变成myfile的地址,不再是显示器文件的地址,所以输出的任何消息都往文件里面写,进而完成重定向。
缓冲方式
- 无缓冲:直接缓冲刷新
- 行缓冲:按行缓冲刷新
- 全缓冲:默认只有缓冲区应写满才会刷新非强制刷新
FILE
- 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以,本质上,访问文件是通过fd访问的。所以c库当中的FILE结构体内部,必定封装了fd。
- 一般c库函数写入文件时是全缓冲的,而写入显示器是行缓冲的
- printf fwrite等库函数自带缓冲区,write系统没有缓冲区说明缓冲区是c库提供的。
- printf fwrite等库函数发生重定向时,数据的写入方式由行缓冲变成了全缓冲。
- 而我们放在缓冲区的数据,不会被立即刷新,甚至fork之后
- 但是我们进程退出之后,会统一刷新,写入文件当中
- fork的时候,父子数据会发生写时拷贝,所以当父进程准备刷新的时候,子进程也就有了同样一份数据,随即产生两份数据
- write没有变化,说明没有所谓的缓冲
inode文件系统
- inode是指在许多Unix文件系统中的一种数据结构。每个inode保存了文件系统的一个文件系统对象(文件的字节数,文件拥有者的ID,所在组ID,文件的读写执行权限,文件的链接数,文件数据lock的位置,文件的时间戳(1.ctime:inode上一次变动的时间2.mtime指文件内容上一次变动的时间3.atime文件上一次打开的时间),文件最后的访问时间ACCESS,文件属性最后的修改时间Change,文件内容最后的修改时间Modify)信息数据,但不包括数据内容或者文件名。
- 文件系统创建(格式化)时,就把存储区域分为两大连续的存储区域。一个用来保存文件系统对象的元信息数据,这是由inode组成的表,每个inode默认是256字节或者128字节。inode节点的字数在格式化时就已经给定了,一般是1kb或每2kb就设置一个inode;另一个用来保存“文件系统对象”的内容数据,划分为512字节的扇区,以及由8个扇区组成的4K字节的块。块是读写时的基本单位。一个文件系统的inode的总数是固定的。这限制了该文件系统所能存储的文件系统对象的总数目。典型的实现下,所有inode占用了文件系统1%左右的存储容量。
-
使用
stat 文件名
可以查看该文件的inode信息 -
使用
ls -li
会显示文件元数据
从左☞右分别表示:文件inode号,权限,硬链接数,文件所有者,组,大小,最后修改时间,文件名 -
ds -i
命令会显示下图信息
-
inode不包含文件名或目录名的字符串,只包含文件或目录的“元信息”。Unix的文件系统的目录也是一种文件。打开目录,实际上就是读取“目录文件”。目录文件的结构是一系列目录项(dirent)的列表。每个目录项,由两部分组成:所包含文件或目录的名字,以及该文件或目录名对应的inode号码。文件系统中的一个文件是指存放在其所属目录的“目录文件”中的一个目录项,其所对应的inode的类别为“文件”;文件系统中的一个目录是指存放在其“父目录文件”中的一个目录项,其所对应的inode的类别为“目录”。可见,多个“文件”可以对应同一个inode;多个“目录”可以对应同一个inode。文件系统中如果两个文件或者两个目录具有相同的inode号码,那么就称它们是“硬链接”关系。实际上都是这个inode的别名。换句话说,一个inode对应的所有文件(或目录)中的每一个,都对应着文件系统某个“目录文件”中唯一的一个目录项。创建一个目录时,实际做了3件事:在其“父目录文件”中增加一个条目;分配一个inode;再分配一个存储块,用来保存当前被创建目录包含的文件与子目录。被创建的“目录文件”中自动生成两个子目录的条目,名称分别是:“.”和“…”。前者与该目录具有相同的inode号码,因此是该目录的一个“硬链接”。后者的inode号码就是该目录的父目录的inode号码。所以,任何一个目录的"硬链接"总数,总是等于它的子目录总数(含隐藏目录)加2。即每个“子目录文件”中的“…”条目,加上它自身的“目录文件”中的“.”条目,再加上“父目录文件”中的对应该目录的条目。通过文件名打开文件,实际上是分成三步实现:首先,操作系统找到这个文件名对应的inode号码;其次,通过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。
-
目录也是文件包括文件名与文件名的inode号,权限r/w针对自己,要读取inode节点内的信息需要执行权限x。
-
创建一个文件的步骤
软硬链接
-
软链接
软链接是通过名字引用另一个文件,
使用ln -s 目标文件
建立软链接 -
硬链接
我们已经了解到真正找到硬盘上的文件并不是文件名,而是inode,在Linux上可以让多个文件名对应于同一个inode。删除一个文件不会影响相同inode的文件。
使用ln 源文件 目标文件
建立硬链接
判断目录几个文件就是硬链接数字减2
- 区别:硬链接没有创文件,只创了文件名与已有的inode建立映射关系。软链接有独立的inode(存放对应目标文件的路径)删除目标文件,打开软链接文件会报错。
上图中file.c与popen.c是硬链接,file1.c与popen.c为软链接。