系统调用的文件操作
一、接口
open():打开文件:
int open(const *pathname, int flags, mode_t mode);
pathname: 要打开文件的路径+名称
flags: 必须指定参数,O_RDONLY只读,O_WRONLY只写,O_RDWR读写; 可附加参数:O_CREAT打开文件不存在则创建,O_TRUNC打开文件后截断,O_APPEND以追加方式打开; 需要组合使用时,可以采用按位或方式“|”
mode: 对于新创建的文件,设置文件权限
返回值: 打开成功,返回文件描述符
write():写入文件
ssize_t write(int fd, const void *buf, size_t count);
fd: 文件描述符
buf: 写入的数据
count: 写入数据大小
read():读取文件
ssize_t read (ind fd, char *buf, size_t count);
buf: 数据存储空间,读到的数据存到哪
count: 最大读取内容,一般是指buf空间大小减1
lseek():根据文件指针的位置和偏移量来定位文件指针
off_t lseek(int fd, off_t offset, int whence);
offset: 偏移量
whence: 定位文件流指针 SEEK_SET,SEEK_END,SEEK_CUR
close():退出文件
close(int fd);
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int fd = open("./tmp.txt", O_RDWR | O_CREAT | O_APPEND, 0664);
if(fd < 0) {
perror("open");
return 0;
}
int ret = write(fd, "linux-66", 8); //写
if(ret < 0) {
perror("write");
return 0;
}
lseek(fd, 5, SEEK_SET);
char buf[1024] = {0}; //读
ret = read(fd, buf, sizeof(buf) - 1);
if(ret < 0) {
printf("read");
return 0;
}
else if(ret == 0)
printf("read size [%d]\n", ret);
printf("%s\n", buf);
close(fd);
return 0;
}
重定向及dup2
int dup2(int oldfd,int newfd); oldfd和newfd都是文件描述符
重定向的本质就是newfd文件描述符拷贝oldfd文件描述符;若oldfd是一个无效的文件描述符,newfd在重定向时不会关闭但调用dup2函数会失败。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("./tmp.txt", O_RDWR | O_CREAT, 0664);
if(fd < 0) {
perror("open");
return 0;
}
dup2(fd, 1); //通过重定向,将本应该输出在标准输出上的内容输出到文件当中去
printf("%s\n", "linux-66");
while(1)
sleep(1);
return 0;
}
命令操作的重定向:'>','>>','<'
eg. echo "hello world" > hello.txt
二、文件描述符与FILE
- 理解: 文件描述符是内核为了高效的管理已经被打开的文件所创建的索引,它是一个非负整数,用于指代被打开的文件,所有执行I/O操作的系统调用都是通过文件描述符完成的。因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
- 文件描述符的分配原则: 最小未分配原则,在files_struct数组当中,找到当前没有被使用
的最小的一个下标,作为新的文件描述符。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
close(0); //关闭标准输入
int fd = open("myfile", O_RDONLY);
if(fd < 0) {
perror("open");
return 1;
}
printf("fd: %d\n", fd); //fd为0
close(fd);
return 0;
}
- 查看当前操作系统为进程所分配的文件描述符信息:|| /proc/[pid]/fd
- 排查文件描述符泄露: ulimit -a ; ll /proc/[pid]/fd
- 文件流指针与文件描述符的关系: 封装关系。文件流指针(接口都是库函数)封装文件描述符(接口都是系统调用)
- 当我们使用fwrite进行操作时,最先写到缓冲区,然后当刷新缓冲区时,才通过_IO-FILE当中保存的文件描述符将写缓冲区当中的内容到文件中去,若程序崩溃有可能导致使用文件流指针写入的数据丢失.。
当使用文件描述符时,没有这个问题,由于立即读取,立即写入的操作非常消耗性能,所以一般不重要的数据采用文件流指针操作
刷新缓冲区:
①main函数return返回时;
②fflush强制刷新;
③缓冲区满会自动刷新;
④\n刷新缓冲区。
静态库和动态库
静态库 动态库
win: .lib后缀 win: .all后缀
linux: .a后缀 linux: .so后缀
不管是使用静态库还是使用动态库,这些库文件都是由*.o文件生成的,在给编译器gcc传不同的参数,就会生成相应的
静态库或者是动态库了
静态库的生成
静态库会将编译的代码当中所有函数的代码全部编入静态库;如果程序链接静态库生成可执行程序时,会将静态库中所有代码全部编译到可执行程序,当程序执行时,不需要依赖静态库。
#include "print.h" //print.c
void print() {
printf("I am print.c file code\n");
}
#include "print.h" //main.c
int main() {
print();
return 0;
}
#include <stdio.h> //print.h
void print();
ar -rc lib[库文件名称].a xxx.o 生成静态库
gcc bbb.c -o bbb -L [path](指定库路径) -l[库文件名称(去前缀和后缀)] 使用静态库
eg.
gcc -c print.c -o print.o 生成.o文件
ar -rc libprint.a print.o 生成静态库
gcc main.c -o main -L . -lprint 使用静态库
./main 执行
I am print.c file code
动态库的生成
#include "print.h" //main.c
int main() {
print();
return 0;
}
#include "print.h" //print.c
void print() {
printf("i am print.c file code\n");
}
#include <stdio.h> //print.h
void print();
gcc -shared(生成共享库的命令行参数) -fPIC(产生位置无关的代码) xxx.c -o lib[库文件名称].so 生成动态库
gcc bbb.c -o bbb -L [path](指定库路径) -l[库文件名称(去前缀和后缀)] 使用动态库
eg.
gcc -shared -fPIC print.c -o libprint.so
gcc main.c -o main -L . -lprint
注:如果程序依赖一个动态库,在程序运行时,它能正常找到库的方式:
- 在当前可执行程序的目录下;
- 在环境变量中设置动态库的搜索路径,设置环境变量LD_LIBRARY_PATH;
- 库在操作系统库(一般不使用)。
三、文件系统
ext2文件系统
-
Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成;
-
超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck和inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了 ;
-
GDT,Group Descriptor Table:块组描述符,描述块组属性信息 ;
-
块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用 ;
-
inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用;
-
i节点表:存放文件属性 如 文件大小,所有者,最近修改时间等 ;
-
数据区:存放文件内容。
-
如何存储文件数据:
①将文件分成不同的block块,从block bitmap当中查找data blocks区域当中空闲的块,将文件存储在空闲块中;
②需要对文件进行表述,否则在获取文件的时候不知道文件分成的块存储再data blocks区域当中的哪一个位置,使用inode bitmap节点去描述文件存储信息,从inode table当中获取inode节点,使用inode节点描述文件存储的信息;
③文件名称+inode节点名称作为目录的目录项存储起来 ll -i。 -
如何获取文件数据:
在目录中根据文件名称+inode节点号,找到inode节点;根据inode节点信息,查找文件对应的信息,将块信息合并,就获取了文件信息。
软链接
in -s [源文件名称] [软链接文件] 源文件和软链接文件inode节点号不同但描述信息都是一样的
若删除源文件,则软链接文件会闪烁,若此时修改软链接文件,则会重新创建源文件(一般不要用)
硬链接
in [源文件名称] [软链接文件]
源文件和软链接文件inode节点号,描述信息都是一样的类似于C++引用(别名)