目录
1. 标准库的IO接口
- fopen:打开文件
- fopen(file,参数)
- 参数
- r:若文件存在,以只读打开;不存在,报错
- r+:若文件存在,以读写打开;不存在,报错
- w:若文件不存在则创建;存在,清空原有内容,以只写打开
- w+:若文件不存在则创建;存在,清空原有内容,以读写打开
- a:若文件不存在则创建;存在,以追加写方式打开
- a+:若文件不存在则创建;存在,以读和追加写方式打开
- 参数
- fopen(file,参数)
- fseek:跳转读写位置
- int fseek(FILE *stream,long offset,int whence);
- 对文件读写位置从whence开始偏移offset个字节
- whence的参数
- SEEK_SET:从文件起始位置开始偏移
- SEEK_CUR:从当前读写位置开始偏移
- SEEK_END:从文件末尾位置开始偏移
- 返回值
- 从文件起始位置到当前跳转位置的偏移量
- int fseek(FILE *stream,long offset,int whence);
- fwrite:写按块入数据
- size_t fwrite(void *ptr,size_t size,size_t nmemb,FILE *stream) ;
- 写数据时,可以分块写入。
- size:块大小 nmemeb:块数量 两者相乘=字节大小
- 返回:写入块个数
- size_t fwrite(void *ptr,size_t size,size_t nmemb,FILE *stream) ;
- fread:读出数据
- fclose:关闭
- printf:将格式化字符串写入到标准输入文件
- fprintf:将多个字符串,格式化为一个,写入指定文件
- sprintf:将字符串格式化后,放入buffer中
- snprintf:对sprintf的一个保护,将字符串拷贝至buffer时,给定大小的限制
- fgets:获取一行数据
- FILE*:文件流指针
#include <stdio.h>
#include <string.h>
int main(){
FILE *fp=NULL;
fp = fopen("./tmp.txt","w+");
if(fp==NULL){
perror("fopen error");
return -1;
}
char buf[1024]="c'est la vie~\n";
//size_t fwrite(void *ptr,size_t size,size_t nmemb,FILE *stream);
fwrite(buf,strlen(buf),1,fp);
//跳转读写位置
//int fseek(FILE *stream,long offset,int whence);
fseek(fp,0,SEEK_SET);
//初始化buf内存
memset(buf,0x00,1024);
//size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);
int ret=fread(buf,1024,1,fp);
//在文件末尾
if(feof(fp)){
printf("at end of file\n");
}else{
perror("fread error");
}
printf("ret:%d--buf:[%s]\n",ret,buf);
fclose(fp);
return 0;
}
2. 系统调用的IO接口
- open
- int open(const char *pathname,int flags,mode_t mode);
- pathname:打开的文件路径名
- flags:选项参数
- 必选其一
- O_RDONLY 只读
- O_WRONLY 只写
- O_RDWR 读写
- 可选项
- O_CREAT 文件存在则打开,不存在则创建
- O_TRUNC 将文件长度截断为0(清空原有内容)
- O_APPEND 追加
- 必选其一
- mode:权限(八进制数字) mode & ~umask
- 返回值
- 成功 -> 文件描述符(正整数,系统调用接口的操作句柄)
- 失败 -> -1
- int open(const char *pathname,int flags,mode_t mode);
- write
- ssize_t write(int fd,const void *buf,size-t count);
- fd:open返回的文件描述符
- buf:要写入的数据
- count:写入的数据长度
- 返回值
- 成功 -> 实际写入的数据长度(字节)
- 失败 -> -1
- ssize_t write(int fd,const void *buf,size-t count);
- read
- ssize_t read(int fd,void *buf,size_t count);
- fd:open返回的文件描述符
- buf:内存首地址,用于获取存储读取的数据
- count:读取的数据长度
- 返回值
- 成功 -> 实际读取的数据长度(字节)
- 失败 -> -1
- ssize_t read(int fd,void *buf,size_t count);
- close
- int close(int fd);
- lseek
- off_t lseek(int fd, off_t offset, int whence);
- fd:open返回的文件描述符
- offset:偏移量
- whence:偏移起始位置
- SEEK_SET:从文件起始位置开始偏移
- SEEK_CUR:从当前读写位置开始偏移
- SEEK_END:从文件末尾位置开始偏移
- 返回值:成功 -> 从文件起始位置到当前读写位置的偏移量 失败 -> -1
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(){
//mode_t umask(mode_t mask);
//设置调用进程的文件创建权限掩码
umask(0);
//int open(const char *pathname,int flags,mode_t mode);
//不存在则创建时没有权限0664,为了确保实际权限时0664,设置umask为0
int fd=open("./test.txt",O_RDWR|O_CREAT|O_APPEND|O_TRUNC,0664);
if(fd<0){
perror("open error");
return -1;
}
char *ptr="la vie est belle.\n";
int ret=write(fd,ptr,strlen(ptr));
if(ret<0){
perror("write error");
return -1;
}
lseek(fd,0,SEEK_SET);
char buf[1024]={0};
//ssize_t read(int fd,void *buf,size_t count);
ret=read(fd,buf,1023);
if(ret<0){
perror("read error");
return -1;
}
printf("read buf:[%d-%s]\n",ret,buf);
close(fd);
return 0;
}
3. 文件描述符
-
3.1 什么是文件描述符
- 进程通过struct file结构体来描述打开的文件 -> struct file *fd_array[]
- 文件描述符就是struct file结构体指针数组的下标
- 用户打开文件,操作系统通过file结构体描述文件,并且将指针添加进入fd_array中,向用户返回这个文件描述信息在数组中的位置(下标)
- 用户操作文件时,将这个下标传递给操作系统,操作系统通过下标找到文件描述信息进而操作文件。
- fd_array[]在pcb的struct files_struct结构体中
- 文件描述符就是struct file结构体指针数组的下标
- 解释
- 该文件存储在硬盘中,当进程打开一个文件后,在进程中使用struct file结构体进行描述,将struct file放入fd_array[],给用户返回一个下标fd,拿着fd去struct files_struct结构体中的fd_array[],通过fd找到文件,进而操作文件。
- 进程的文件描述符是有上限的
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
close(1);
int fd=open("./test.txt",O_RDWR);
if(fd<0){
perror("open error");
return -1;
}
//\n刷新缓冲区,仅仅针对标准输出文件才有效
//其他文件,\n仅仅具备换行功能
printf("fd:%d\n",fd);
fflush(student);
//close是系统调用接口,没有缓冲区,直接关闭,数据没有刷新至文件中
//我们所说的缓冲区是stdout的缓冲区(用户态的缓冲区)
close(fd);
return 0;
}
-
3.2 文件描述符的分配规则
- 最小未使用原则
- 在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
- 默认从3开始
- ∵ 一个进程运行起来后,默认会打开三个文件
- 标准输入 0 STDIN_FILENO
- 标准输出 1 STDOUT_FILENO
- 标准错误 2 STDERR_FILENO
- ∵ 一个进程运行起来后,默认会打开三个文件
- 最小未使用原则
-
3.3 文件流指针与文件描述符的关系
- 文件流指针
- 文件库函数的操作句柄
- stdin、stdout、stderr
- 文件库函数的操作句柄
- 文件操作符
- 系统调用接口的句柄
- 文件流指针这个FILE结构体中就包含了一个文件描述符成员变量
- 解释:
- 打开文件test.txt,通过struct file结构体进行描述,将struct file放入fd_array[],给用户返回一个下标fd,通过fd进行操作。系统调用使用File *fp,通过fwrite(fp)写入数据,假设其调用write(fd),需要通过fp找到fd
- 解释:
- 文件流指针
-
3.5 缓冲区位置
系统调用接口没有缓冲区,标准库中实现的缓冲区,fflush(stdout)刷新标准输出缓冲区。文件流指针结构体中描述了一个缓冲区,File *fp中有两个缓冲区(写入缓冲区、读取缓冲区)。
-
3.6 通过文件流指针操作一个文件
- 写入数据(用户态缓冲区):
- 写入到文件流指针包含的缓冲区中,刷新缓冲区或缓冲区满了的时候,将数据写入文件。即开始真正的操作文件
- 使用系统调用接口
- 系统调用接口通过fp找到fd
- 通过fd寻找文件描述信息
- 通过文件描述信息去操作文件
- 实现将数据写入到文件中
- 写入数据(用户态缓冲区):
/*缓冲区*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
printf("%s","printf");
fprintf(stdout,"%s","fprintf");
fwrite("fwrite",6,1,stdout);
write(1,"write",5);
sleep(3);
return 0;
}
-
3.7 内核态与用户态
- 进程运行在用户态 / 进程运行在内核态:
- 当我们发起系统调用调用某个硬件去完成某个系统功能,这些功能需要操作系统完成 -> 进程运行在内核态
- 当前操作没有发起系统调用,当前的系统功能并非是由系统内核完成 -> 进程运行在用户态
- 进程如何从用户态切换到内核态运行 -> 发起系统调用,使用系统调用接口
- 进程运行在用户态 / 进程运行在内核态:
4. 重定向
改变数据流向,将写入指定文件的数据改变之后写入到另一个文件。
- 本质
- 描述符的重定向 -> 描述符并没有改变,改变的是描述符这个下标所对应的文件,描述信息
- 操作相同的描述符,但是具体操作的文件已经改变
-
4.1 dup2
- int dup2(int oldfd, int newfd);
- 功能
- newfd本身指向b.txt,经过dup2接口后,newfd指向了a.txt,并且关闭b.txt。这时,oldfd和newfd指向的是同一个文件a.txt。
- 即,让newfd也指向oldfd所指向的文件,并且如果newfd本身已经打开了文件,则关闭原先打开的文件。这时,oldfd和newfd操作的都是oldfd所指向的文件a.txt。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd=open("./test.txt",O_RDWR);
if(fd<0){
perror("open error");
return -1;
}
//将要打印的数据不写入标准输出,而是写入test.txt
//1是标准输出
dup2(fd,1);
//\n刷新缓冲区,仅仅针对标准输出文件才有效
//其他文件,\n仅仅具备换行功能
printf("fd:%d\n",fd);
fflush(student);
//close是系统调用接口,没有缓冲区,直接关闭,数据没有刷新至文件中
//我们所说的缓冲区是stdout的缓冲区(用户态的缓冲区)
close(fd);
return 0;
}
-
4.2 在minishell中添加重定向
- 5.2.1 重定向方式
- > 清空原有内容
- >> 追加新内容
- 5.2.2 实现ls -l >> tmp.txt
- 1.检索命令中是否有重定向功能
- 2.命令分隔[ls -l] [tmp.txt] (将>>转换为/0)
- 3.判断重定向方式
- > O_TRUNC
- >> O_APPEND
- 4.创建子进程
- 5.子进程实现重定向 -> 命令解析 -> 程序替换
- 6.父进程等待子进程
- 5.2.1 重定向方式
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
char buf[1024]={0};
char *argv[32];
int argc=0;
//显示信息,获取字符串
int do_face(){
//等待标准输入
printf("[qqy@localhost]$ ");
fflush(stdout);
//%[^\n] 从标准输入缓冲区中获取一个字符串,遇到换行为止
%*c 将一个字符从缓冲区中取出(丢弃缓冲区中的/n)
!=1代表buf中没有获取到数据
if(scanf("%[^\n]%*c",buf)!=1){
getchar(); //将回车取出
return -1;
}
return 0;
}
//解析字符串
int do_parse(){
argc=0;
//buf[ ls -a]如何解析
//ls是程序名称,也是第0个参数
//-a是第1个参数
//检测空白字符
//
char *ptr=buf;
//直到字符串结束
while(*ptr!='\0'){
//当前位置是非空白字符
if(!isspace(*ptr)){
argv[argc++]=ptr;
//不是空白符号,也没有结束
while(!isspace(*ptr) && *ptr!='\0'){
ptr++;
}
}
//设置字符串结束标志
*ptr='\0';
ptr++;
}
//参数需要用NULL结尾
argv[argc]=NULL;
return 0;
}
//判断内建命令
int build_in(){
//内建命令,shell内部实现其功能,无需创建子进程
if(strcmp(argv[0],"cd")==0){
//int chdir(const char *path);
//改变当前路径
chdir(argv[1]);
return -1;
}
return 0;
}
//判断重定向
int redirect_flag=0;
char *redirect_file=NULL;
int do_redirect(){
//ls -l >> tmp.txt
redirect_flag=0;
redirect_file=NULL;
char *ptr=buf;
while(*ptr !='\0'){
//判定是否为>
if(*ptr == '>'){
redirect_flag=1;
*ptr='\0';
ptr++;
//判定是否为>>
if(*ptr=='>'){
redirect_flag=2;
*ptr='\0';
ptr++;
}
//走到文件名
while(*ptr!='\0' && isspace(*ptr)){
ptr++;
}
//指向文件名
redirect_file=ptr;
//将文件名后面的空格走完
while(*ptr != '\0' && !isspace(*ptr)){
ptr++;
}
*ptr='\0';
}
ptr++;
}
return 0;
}
int main(){
int ret=0;
while(1){
ret=do_face();
if(ret<0){
continue;
}
ret=do_redirect();
if(ret<0){
continue;
}
ret=do_parse();
if(ret<0){
continue;
}
ret=build_in();
if(ret<0){
continue;
}
//创建子进程,执行任务
int pid=fork();
//创建子进程失败
if(pid<0){
continue;
}else if(pid==0){
//子进程,执行其他任务 -> 程序替换
int fd=1;
if(redirect_flag == 1){
//清空重定向
fd=open(redirect_file,O_WRONLY|O_CREAT|O_TRUNC,0664);
if(fd<0){
exit(-1);
}
}else if(redirect_flag==2){
//追加重定向
fd=open(redirect_file,O_WRONLY|O_CREAT|O_APPEND,0664);
if(fd<0){
exit(-1);
}
}
//将标准输出重定向
dup2(fd,1);
if(execvp(argv[0],argv)<0){
perror("");
}
//替换失败 -> 退出
exit(0);
}
//父进程等待子进程退出,避免出现僵尸进程
wait(NULL);
}
return 0;
}
5. 文件系统(ext2)
磁盘上的文件管理系统
- 除了swap交换分区,每一个磁盘分区都会有一个文件系统
- 包含
- linux-ext2-超级块、inode块、数据块、inode_bitmap\data_bitmap
- 目录文件
- 一个用于记录目录下的文件信息(文件名、inode节点号 -> 目录项)的普通文件
-
5.1 文件存储与读取
- 在分区中存储文件,一个一个的进行存储,很快就会将磁盘存满。只剩一点点空间时,无法存储大文件。磁盘利用率过低,查找效率低。
- 因此,进行磁盘分块管理,每一块4096个字节。
- 5.1.1 inode节点
- 若遇到超过4096字节的文件,将文件分块存储,可能会存到不同的磁盘块。因此,需要使用inode节点记录数据存储位置。
- 每个文件都有一个inode节点去记录描述文件信息
- 内容
- 大小
- 权限
- 时间
- 数据存储地址
- inode节点存储于存储文件的inode节点区域
- 查看inode节点大小
- sudo dumpe2fs -h/dev/sda1|grep "Inode size"
- 5.1.2 位图区域
- data_bitmap,标记数据块的使用情况
- inode_bitmap,标记inode的使用情况
- 5.1.3 超级块
- 格式化文件系统、分区时,在超级块中记录分区的文件系统类型、inode节点个数和起始地址、数据块个数和起始地址等统筹信息。
- 5.1.4 文件存储过程
-
通过inode_bitmap在inode区域获取空闲inode节点
-
通过data_bitmap获取空闲数据块
-
在inode节点中记录文件信息以及数据块位置
-
将文件数据写入到数据块中
-
将自己的目录项信息添加到所在目录文件中
-
- 5.1.5 文件存储过程
-
通过文件名在目录项中获取文件inode节点号(文件唯一)
-
通过inode节点号在inode区域找到inode节点
-
通过inode节点中的数据块地址信息,在指定数据块读取数据
-
将文件数据写入到数据块中
-
将自己的目录项信息添加到所在目录文件中
-
-
5.2 软链接与硬链接
- 5.2.1 软链接文件
- 创建
- ln -s srcfile destfile
- 软链接文件与源文件的inode节点号不同,其数据区域存储的是源文件的路径,通过路径找到软链接文件的目录项,通过目录项获取源文件的节点号,进而操作源文件。
- 相当于文件快捷方式,是一个独立的文件
- 创建
- 5.2.2 硬链接文件
- 创建
- ln srcfile destfile
- 硬链接文件与源文件的inode节点号相同,表明硬链接文件只是一个目录项,而不是一个实质性的文件。
- 相当于文件名(目录项),与原文件共用同一个inode节点
- 创建
- 5.2.3 区别
- 删除源文件,软链接文件将失效(通过文件名查找源文件);硬链接无影响(通过inode节点找文件,链接数-1)
- 软链接可以跨分区创建,硬链接不可以(软链接只针对文件名,而硬链接需要inode节点)
- 软链接可以对目录创建,硬链接不可以(目录结构是跨分区的)
6. 动态库与静态库
-
6.1 动态库的生成
- 命名
- lib是前缀
- .so是后缀
- 中间是库名称
- 命令
- gcc -fPIC -c child.c -o child.o
- -fPIC:产生位置无关代码
- gcc --share child.o -o libmychild.so
- --share:指定gcc要生成动态库而不是可执行程序
- gcc -fPIC -c child.c -o child.o
- 命名
-
6.2 静态库的生成
-
命名
- lib是前缀
- .a是后缀
- 中间是库名称
- 命令
- gcc -c child.c -o child.o
- ar -cr libmychild.a child.o
- ar:静态库打包所用命令
- -c:创建
- -r:模块替换
-
-
6.3 库的使用
- gcc main.c -o main -lmychild
- -l 指定链接的库名称
- 报错,因为链接库时,库文件有默认的搜索路径 /lib64/lib/usr/lib64...
- 解决
- 将库文件放置到指定目录下 /lib64
- 设置环境变量
- 库文件的链接搜索路径:LIBRARY_PATH=.
- 库文件的运行加载路径:LD_LIBRARY_PATH=.
- 在gcc生成可执行程序时,直接指定库的搜索路径
- gcc main.c -o main -L . -lmychild
- gcc
- -L:指定库的链接搜索路径
- -l(小L):指定链接库的名称
- -I(大i):指定头文件搜索路径
- gcc
- gcc main.c -o main -L . -lmychild
- 使用第三方静态库,不能使用gcc的-static选项
- -static选项是生成静态链接可执行程序,所有的库都使用静态库(是希望第三方库使用静态库,而不是所有)
- 解决
- 将第三方静态库拷贝到指定路径下,使用-L选项指定库的链接搜索路径。这时,链接的就是静态库。
- gcc main.c -o main -lmychild