C库封装的几个常用文件操作接口:fopen、fwrite、fread、fseek、fclose。
下面用代码实现这几个:
#include
#include
int main()
{
FILE* fp=fopen("./Demo","a+"); //打开当前目录下名为Demo的文件
if(!fp)
{
perror("fopen");
return -1;
}
char buf[1024]={0};
strcpy(buf,"Linux");
int w_ret=fwrite(buf,1,5,fp); //把buf的内容写入文件
printf("w_ret:%d\n",w_ret);
fseek(fp,0,SEEK_SET); //使文件流指针偏移到头
memset(buf,0,sizeof(buf)); //清空buf
int r_ret=fread(buf,1,sizeof(buf)-1,fp); //读出来
printf("r_ret:%d\n",r_ret);
printf("buf:%s\n",buf);
fclose(fp);
return 0;
}
运行结果:
w_ret:5
r_ret:5
buf:Linux
C语言知识在这就不做过多的解释了,下面主要介绍操作系统提供的接口
open、read、close、lseek:
在使用这些系统调用接口时,应该包含头文件:
#includ e
#include
#include
pathname: 要打开或创建的文件
flag: 打开文件时可以传入多个参数选项
O_RDONLY :只读 八进制 0
O_WRONLY :只写 1
O_RDWR :可读写 2
O_CREAT :不存在则创建文件 100
返回值:
成功:返回打开的文件描述符
失败:返回-1
示例:
int fd = open(“path”,O_RDWR | O_CREAT,0664)可读写,没有创建并且文件权限664(八进制)->对应二进制 110 110 100
O_REWR | O_CREAT: 这里用两者的或来表示权限
原因:参数在都是以八进制码存储的
例如:O_REWR :可读可写,八进制–>2 ,对应二进制 00000000 00000000 00000000 00000010,二进制的低八位,每一位都代表了不同权限,两者或的结果就可实现:可读可写没有文件则创建一个
O_RDWR | O_CREAT
00000000 00000000 00000000 00000010 八进制2 ,可读可写
00000000 00000000 00000000 01000010 八进制100 ,没有文件则创建
按位或(有1则为1)
00000000 00000000 00000000 01000010
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
off_t lseek(int fd, off_t offset, int whence);
int close(int fd);
操作系统会给每个进程在磁盘中创建一个以进程号(pid:进程标识符)命名的文件夹,该文件下有一个fd文件夹,保存的是文件描述符,新创建的进程会打开三个文件描述符,分别对应,标准输入(0),标准输出(1),标准错误(2),不关闭前三个则打开其他(3),用完但没有关闭会造成句柄泄露
下面是查看文件描述符的操作:
注意:进程退出之后这个文件就被清除了,因此可在程序中写死循环,新开一个终端查看
下面介绍进程是如何和文件建立起联系的:
创建一个进程,相当于创建了一个task_struct结构体,在这个结构体里有一个类型为files_struct的结构体指针files专门用来保存该进程已打开文件的信息,其中有指针数组fd_array,数组中每一个的每一个元素都是一个指向打开文件的指针。所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符(保存着文件原信息,文件大小,权限等等),就可以找到对应的文件
结论:
1.文件描述符其实就是指针数组fd_array[ ]的下标
2.文件描述符是一个正整数,分配原则为最小未占用原则
文件描述符的分配规则:
#include
#include
#include
#include
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
输出结果:fd:3
那么关闭文件描述符0或者2,再看
#include
#include
#include
#include
int main()
{
close(0); //关闭标准输入
//close(2); //关闭标准错误
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符
文件流指针也就是FILE* fp中有一个指针int _fileno;保存的就是文件描述符的数值
1)文件流指针是fopen函数返回的,是库函数维护的
2)文件描述符是open函数返回的,是内核维护的
C语言中FILE 在操作系统中其实是struct _IO_FILE 结构体:不同文件流指针会创建不同的IO结构体,保存了不同的文件描述符,文件流指针里保存着很多文件描述符
c库维护的exit函数在退出时,会刷新缓冲区,而操作系统接口_exit函数不会刷新缓冲区
FILE是typedef struct _IO_FILE,里面保存着读写缓冲区(C库中的东西),最下面还有一个指针int _fileno,保存的就是文件描述符的数值,exit()退出时刷新缓冲区就是读缓冲区,操作的是文件流指针,而_exit()无法刷新是因为它是系统调用接口
int dup2(int oldfd, int newfd);把newfd文件描述符指向改为oldfd文件描述符指向
#include
#include
#include
#include
int main()
{
int fd=open("./Linux",O_RDWR | O_CREAT,0664);
dup2(fd,1); //也就是把当前进程标准输出修改成了fd的路径(fd的路径:指向当前目录下的Linux文件)
printf("fd:%d\n",fd);
printf("正在学习~~\n");
close(fd);
return 0;
}
这段代码没有任何输出,但在Linux文件中可以看到:
fd:3
正在学习~~
我们发现,本来应该输出到显示器上的内容,输出到了文件Linux当中,其中,fd=1。这种现象叫做输出重定向。
重定向的本质:就是修改了文件描述符的指向
当打开一个文件时,系统会给程序分配一个文件描述符,如果使用完毕没有关闭文件,就会造成文件句柄泄露
一个进程当中最大打开的文件数量是多少?
在终端中输入:ulimit -a,可以看到:open files (-n) 1024
1.默认打开文件数量1024 其实真实并不是1024,(3~1023)要减去默认的三个文件,0:标准输入,1:标准输出,2:标准错误 ,所以实际打开文件1021
2.ulimit -n[num]更改最大打开数量为num,ulimit -a 查看设置权限
例:ulimit -n2020
线性存储方式可能会导致磁盘利用率较低,离散式存储能提高磁盘利用率,ext2文件系统就是采用了离散式存储,每个比特位表示Data blocks中块的使用情况,为1表示占用,0表示空闲。
创建一个新文件有以下操作:
1.在BlockBitMap中查找空闲block块,将文件存储在空闲的block块当中。
2.再通过inode BitMap获取空闲的inode节点,内核在空闲的inode节点中描述文件的属性和位置。
3.添加文件名到目录,文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。
要在磁盘中找一个文件,要找的是inode,拿到inode也就获取的文件地址
硬链接: 类似引用,inode和原文件相同
软链接: 类似于快捷方式,inode和原文件不同
创建一个硬链接:ln file1 file2
创建一个软链接:ln -s file1 file2
ll -i 可以看到软链接的inode节点号不同,软链接文件具有独立的inode节点号,而硬链接两个的inode节点一模一样,一个文件生成了两个有效路径
1.理解进程打开或创建一个文件,要通过哪些步骤
2.理解文件在磁盘中的存储方式和读取
3.理解文件描述符