文章目录
1.当前路径
进程在启动的时候,自动记录自己启动时所在的路径就叫当前路径。
进程创建文件,如果只说明了文件名,那么操作系统是向当前路径进行创建。
1.当前路径怎么查看?
在/proc/路径下的对应进程pid的目录中,有一个cwd文件,指向了进程对应的当前工作目录。
2.如何改变当前路径?
使用系统调用
其中chdir函数的参数path是字符串,字符串的内容是指定的目录路径。
而fchdir的参数指定的是fd对应的文件目录,fd这个参数会在后面进行介绍。
2.C标准库的常用文件函数
FILE* fopen(const char* path,const char* mode);
int fclose(FILE* fp);
int fputs(const char* s,FILE* stream);
char* fgets(char* s,int size,FILE* stream);
size_t fread(void * ptr, size_t size, size_t nmemb, FILE* stream );
size_t fwrite(const void * ptr, size_t size, size_t nmemb, FILE* stream);
fopen:C语言封装的打开文件的函数,path是文件路径,mode是权限。
fclose:关闭文件,fp是文件对应的File指针
fputs:s是要输出的字符串,stream是要输出的文件对应的File指针,不会自动换行,换行需要自己加上\n
fgets:从stream对应的文件里读一个字符个数最多为size-1的字符串,遇到换行符会提前结束并在下一次读取的从下一行开始读,如果没读到\n不会自动换行,下次读的时候会继续接着上次读的结尾往后读
fread://二进制输入函数,从二进制文件中提取nmemb数量的大小为size的数据到ptr指向的空间里,返回值是实际读到的个数,<=nmemb
fwrite://二进制输出函数,ptr是要读取数据的地址,size是写入数据的大小(字节),nmemb是写几个size大小的数据,stream是写入文件对应的File*,输出的是二进制文件
3.什么是File
File是C语言封装的一个结构体,里面包含了文件的一些属性,其中就有我们之前提到的fd,还有哪些属性,这里不做描述。
4.文件描述符——fd
fd是操作系统管理文件所描述的一个整形变量。
操作系统要管理文件,必定要让文件先加载到内存,然后先描述,再组织,内核中要有描述对应文件的结构体——也就是struct file,和C语言的File不同。
struct file中最核心的数据可以分为3大类,一个是属性,一个是方法集,一个是缓冲区。
属性里面就有fd,而fd是干什么的呢,这就要和进程牵扯到一起,因为文件是由进程发起创建的。
进程的pcb(也就是task_struct)中有一个结构体指针struct file_struct* files,指向的结构体中有一个指针数组struct file* fd_array[],也就是存放了描述文件的结构体指针,指向了进程所打开文件的结构体,而fd就是该结构体在数组中的下标。
因此,显而易见,fd的作用就出来了,通过进程的指针struct file_struct* files找到该指针数组,再通过fd找到指定数组下标中的struct file,从而对文件进行操作。
文件描述符的分配规则
当进程打开时,操作系统默认会打开三个文件流
这三个文件流分别对应标准输入流、标准输出流、标准错误流,它们的File指向的File中都有对应的fd,因此,数组struct file fd_array[]的默认前三个下标0、1、2分别保存了这三个文件流对应的struct file*。
当进程打开文件时,会在struct file*数组中找到当前没有被使用的最小的下标,作为新的文件标识符。
如果在打开文件之前,把这个三个文件流关闭,那么就会自动分配到当前的最小下标。
5.管理文件的系统调用
C语言库函数的文件操作函数实际上都是封装了操作系统所给的系统调用,因此,弄懂了系统调用,任何语言的封装都能够明白。
1.open
OS提供了两种open系统调用,pathname也就是指定文件的文件路径,flags是打开文件选择的选项,mode是权限。
flags:
在C语言中,mode是可以是只读"r",只写"w",读写"rw"等等,这在底层中实际上就是选项的|,选项的|,相信很多人都一头雾水,选项还能|吗?
实际上,系统调用提供了很多选项
- O_RDONLY: 只读方式打开文件。
- O_WRONLY: 只写方式打开文件。
- O_RDWR: 读写方式打开文件。
- O_CREAT: 如果文件不存在则创建新文件。
- O_EXCL: 与 O_CREAT 同时使用,如果文件存在则报错。
- O_TRUNC: 如果文件存在且为写权限,则将其长度截断为0。
- O_APPEND: 在文件末尾追加数据而非覆盖原有内容。
- O_NONBLOCK: 非阻塞方式打开文件。
- O_SYNC: 同步写入数据,确保数据实际写入磁盘。
- O_DIRECTORY: 如果参数不是目录,则打开失败。
- O_NOFOLLOW: 如果参数是一个符号链接,则打开失败。
- O_DIRECT: 直接 I/O,绕过操作系统缓存。
- O_CLOEXEC: 执行exec调用时关闭文件描述符。
- O_TMPFILE: 创建一个匿名临时文件。
这些选项都是宏,一个int类型有32个bit位,通过bit位上的数字是否为1表示是否有这个选项,而这些选项都是一个bit位上是1,其余bit位上是0,因此|的时候,只有对应选项的带1的bit位是1,其余没选的都是0,这就达到了我们想要多个选项的效果,也就是数据结构里的位图。
mode:代表的是文件被创建时的读写权限,如果对文件的权限进行修改,也就是ugo(user、group、others)的读写执行权限,会受到umask掩码的影响,如果想修改umask,也可以调用系统调用。
2.close
关闭对应文件描述符fd的文件,也就是令struct file* fd_array[]对应下标fd指向空。
3.read
从文件描述符fd的文件中读取count个字节的数据到buf中。
4.write
向文件描述符fd的文件中写入buf中count(可能会低于count)个字节的数据。
6.操作系统怎么实现对不同的硬件进行读写
先描述再组织,每个不同的硬件,操作系统对应的读read、写write方法一定不相同。
通过创建对应硬件的struct file,有一个结构体指针变量struct file_operation*(方法集)指向的结构体中包含了对应文件的各种操作的函数指针,通过函数指针指向对应硬件的读写方法,转为了上层调用文件的读写方法,而文件的读写方法实际上就是方法集中所指向的不同硬件的读写方法,这样就可以屏蔽硬件之间的差异。
7.重定向的理解
当我们将stdout文件流关闭,即close(1),再打开一个文件时,根据分配规则,该文件会被分配到数组struct file* fd_array[]下标为1的位置,并printf函数打印内容,代码完成编译后,运行会发现,显示屏上并没有打印出内容,那么内容到了哪里呢?——实际上是打印到了文件中,实现了和重定向一样的功能。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
close(1);
int fd=open("login.txt",O_WRONLY|O_CREAT|O_TRUNC);
printf("fd=%d\n",fd);
return 0;
}
所以,我们可以推测,重定向的实现,实际上是Shell对命令进行解释,然后再由底层对标准输出流进行关闭,而由于对文件操作要打开文件,就使得文件被分配到了标准输出流的下标上,输出只是对下标为1的文件进行输出,而并不关心下标为1的文件具体是什么。