当前位置: 首页 > news >正文

大良营销网站建设市场网页版梦幻西游勇闯火焰山攻略

大良营销网站建设市场,网页版梦幻西游勇闯火焰山攻略,做微课的网站有哪些,在线视频播放网站建设一、理解文件 狭隘理解#xff08;传统视角#xff09; 聚焦物理存储#xff1a;文件特指存储在磁盘等外存设备上的二进制数据集合输入输出特性#xff1a; 写入文件#xff1a;CPU 通过总线将数据输出到磁盘读取文件#xff1a;磁盘通过 DMA 将数据输入到内存 #xff…一、理解文件 狭隘理解传统视角 聚焦物理存储文件特指存储在磁盘等外存设备上的二进制数据集合输入输出特性 写入文件CPU 通过总线将数据输出到磁盘读取文件磁盘通过 DMA 将数据输入到内存 DMADirect Memory Access即直接存储器访问它允许某些硬件子系统可以独立地直接读写计算机内存而不需要经过中央处理器CPU从而提高数据传输的效率。例如在硬盘与内存之间的数据传输中可以使用 DMA 来减少 CPU 的负担加快数据传输速度。 典型操作open/read/write/close 等标准文件操作接口 Linux 广义抽象 设备文件化 硬件设备映射为 /dev 目录下的特殊文件如 /dev/sda 代表磁盘进程间通信管道也被视为文件如命名管道文件 统一操作接口 所有设备都通过文件描述符fd操作实现方式驱动程序提供 file_operations 结构体 典型案例 键盘输入/dev/console网络通信/dev/net/tun随机数生成/dev/urandom 这种设计的核心优势在于 实现 一处代码多处复用 的设备无关性进程可以用相同的系统调用操作不同设备极大简化了设备管理和驱动开发的复杂度 例如当我们使用 cat 命令读取文件时实际上是在读取磁盘设备文件而当使用 cat /dev/tty 时数据就会直接输出到终端设备两者使用的都是 read/write 系统调用。 归类认知 文件 文件内容 文件属性对于0KB的空文件是占用磁盘空间的所有的文件操作本质是文件内容操作和文件属性操作对文件的操作本质是进程对文件的操作磁盘的管理者是操作系统文件的读写本质不是通过C/C的库函数来操作的而是通过文件相关的系统调用接口来实现的C/C库函数对系统调用进行了封装。 二、C文件接口  fopen打开文件 #include stdio.h ​​​​​​​FILE *fopen(const char *filename, const char *mode); 功能打开指定名称的文件并返回一个指向该文件的 FILE 指针。如果打开失败返回 NULL。参数 filename要打开的文件的名称可以包含路径。mode指定文件的打开模式常见的模式有 rread以只读模式打开文本文件。wwrite以写入模式打开文本文件如果文件不存在则创建如果文件已存在则清空内容shell的输出重定向就是这个原理。aappend以追加模式打开文本文件如果文件不存在则创建shell的追加重定向就是这个原理。rb、wb、ab分别对应二进制文件的只读、写入和追加模式 例子 #include stdio.hint main() {FILE *fp fopen(test.txt, w);if (fp NULL) {perror(Failed to open file);return 1;}// 文件操作代码fclose(fp);return 0; } fclose关闭文件 #include stdio.h int fclose(FILE *stream); 功能关闭指定的文件流。如果关闭成功返回 0否则返回 EOF。同时会刷新用户态标准I/O库缓冲区参数 stream指向要关闭的文件的 FILE 指针。 fwrite 二进制写入文件wb size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); ptr指向要写入的数据的指针const void * 类型表示可以指向任意类型的数据。size每个数据项的大小以字节为单位。nmemb要写入的数据项的数量。stream指向目标文件的 FILE 指针该文件通常以写入模式如 wb 用于二进制写入打开。返回实际成功写入的数据项的数量。如果返回值小于 nmemb可能表示发生了错误或者到达了文件末尾。 fread二进制读取文件rb size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); ptr指向用于存储读取数据的缓冲区的指针。size每个数据项的大小以字节为单位。nmemb要读取的数据项的数量。stream指向源文件的 FILE 指针该文件通常以读取模式如 rb 用于二进制读取打开。返回实际成功读取的数据项的数量。如果返回值小于 nmemb可能表示发生了错误或者到达了文件末尾。 其他相关C文件操作的库函数还有 fputc写入字符原型int fputc(int c, FILE *stream); fgetc读取字符原型int fgetc(FILE *stream); fputs写入字符串原型int fputs(const char *s, FILE *stream); fgets读取字符串原型char *fgets(char *s, int size, FILE *stream); fprintf写入格式化数据原型int fprintf(FILE *stream, const char *format, ...); fscanf读取格式化数据原型int fscanf(FILE *stream, const char *format, ...); ftell返回当前文件指针的位置。如果发生错误返回 -1L。 fseek将文件指针移动到指定的位置。如果成功返回 0否则返回非零值 原型 int fseek(FILE *stream, long offset, int whence);stream指向目标文件的 FILE 指针。 offset偏移量以字节为单位。 whence指定偏移的起始位置有三个可选值 SEEK_SET从文件开头开始偏移。 SEEK_CUR从当前文件指针位置开始偏移。 SEEK_END从文件末尾开始偏移。 本文并非对这些接口进行详细讲解如有需要可以跳转之前的博客文件与文件操作_1文件-CSDN博客  编译并执行proc #include stdio.h #include stdlib.hint main() {FILE *file fopen(myfile,w);if(!file){perror(myfile open);exit(1);}fputs(hello world\n,file);fclose(file);return 0; }myfile在proc执行之前是不存在的打开文件使用w模式如果打开文件不存在就会在当前工作路径下创建这个文件。 cwd当前工作路径指的是当前进程所在的路径会随着进程在不同的路径而发生改变可使用系统调用chdir来改变当前工作路径使用系统调用getcwd来获取当前工作路径 exe指的是启动该进程的路径在启动进程时就确定了 打开文件本质是进程打开的进程通过当前工作路径知道自己在哪里所以文件不存在且文件不带路径进程通过当前工作路径也能够在该路径下创建文件。 实现简单cat命令 #include stdio.h #include stdlib.h #include unistd.hint main(int argc, char *argv[]) {if(argc ! 2){printf(argv error\n);exit(1);}FILE *file fopen(argv[1],r);if(!file){perror(file fopen);exit(1);}char buf[1024];while(1){int size fread(buf,sizeof(char),sizeof(buf),file);if(size 0){buf[size] \0;printf(%s,buf);}if(feof(file)){break;}}printf(\n);fclose(file);return 0; }三、C语言的输入输出流 C会默认打开三个输入输出流分别是stdin、stdout、stderr这三个流的类型都是FILE* 文件指针。 stdin标准输入对应的外设键盘stdout标准输出对应的外设显示器stderr标准错误对应的外设显示器 既然stdout是标准输出流类型是FILE*对应的外设是显示器我们说Linux下一切皆为文件那么显示器也可以当成文件所以可以通过文件操作函数将信息输出到显示器 #include stdio.h #include unistd.h #include stdlib.hint main() {char s[] hello linux\n;fwrite(s,sizeof(char),sizeof(s),stdout);printf(hello world\n);fprintf(stdout,hello hhhh\n);return 0; }stdout和stderr对应的外设都是显示器但它们在机制上存在差异 缓冲机制 stdout stdout 通常是行缓冲line-buffered或全缓冲fully-buffered的。行缓冲在遇到换行符\n时缓冲区中的内容会被刷新并输出到终端。例如使用 printf(Hello, World!\n); 时Hello, World! 会立即输出。全缓冲只有当缓冲区满或者调用 fflush(stdout) 函数时缓冲区中的内容才会被输出。在某些情况下例如程序重定向输出到文件时stdout 可能会变成全缓冲模式。这里使用标准I/O库函数进行文件操作刷新的是用户态标准 I/O 库缓冲区使用系统调用进行文件操作刷新的是内核缓冲区。 stderr stderr 是无缓冲unbuffered的。这意味着一旦有数据写入 stderr数据会立即被输出到终端不会进行缓冲等待。这种设计确保了错误信息能够及时显示避免因缓冲导致错误信息显示不及时而影响调试。 输出重定向的影响 stdout 可以很方便地将 stdout 的输出重定向到文件或其他设备。在类 Unix 系统的命令行中可以使用  符号进行重定向。例如./a.out output.txt 会将程序 a.out 的 stdout 输出重定向到 output.txt 文件中。 stderr stderr 的输出不会受到 stdout 重定向的影响。如果只对 stdout 进行重定向stderr 的信息仍然会输出到终端。不过也可以单独对 stderr 进行重定向在类 Unix 系统中可以使用 2 符号例如 ./a.out 2 error.txt 会将程序 a.out 的 stderr 输出重定向到 error.txt 文件中。 #include stdio.h #include unistd.h #include stdlib.hint main() {printf(hello\n);fprintf(stderr,hello world\n);return 0; }   在C中默认打开的三个流为cin、cout、cerr分别对应标准输入、标准输出、标准错误。 四、系统文件I/O fopen、ifstream等流式属于语言层的方案对系统调用进行了封装系统调用才是打开文件最底层的方案。 介绍一种传递标志位的方法 假设设计一个函数根据传递的参数不同而实现不同的功能这就可以使用位图的结构将每一位设置为每一种功能的标志位。使用可以鉴别各个功能的存在使用 | 可以使一个位图传递多个功能。 #include stdio.h #include stdlib.h #include unistd.h#define ONE 1 //0001 #define TWO 2 //0010 #define THREE 4 //0100void func(int flag){if(flag ONE) printf(func of ONE\n);if(flag TWO) printf(func of TWO\n);if(flag THREE) printf(func of THREE\n);printf(\n); }int main() {func(ONE);func(TWO);func(THREE);func(ONE | TWO);func(TWO | THREE);func(ONE | TWO | THREE);return 0; }系统调用open 函数原型int open(const char *pathname, int flags, mode_t mode);参数解析 pathname要打开或创建的文件的路径名可以是绝对路径如/home/user/test.txt或相对路径如test.txt相对当前工作目录。flags指定打开文件的方式和选项常用取值如下 O_RDONLY以只读方式打开文件。O_WRONLY以只写方式打开文件。O_RDWR以可读可写方式打开文件。O_CREAT如果文件不存在则创建新文件。O_EXCL与O_CREAT一起使用确保文件创建时不存在否则返回错误。O_APPEND以追加模式打开文件每次写入数据时数据将追加到文件末尾。这些选项都是每个二进制位表示一个功能与传递标志位的方法一样。头文件fcntl.h mode当O_CREAT标志被指定时用于指定新文件的访问权限。取值通常使用八进制表示如0644表示所有者可读可写组用户和其他用户只读 需要注意每个启动的进程都设置有文件权限掩码例如为0002实际文件权限 设置的文件权限值 - 文件权限掩码0644 - 0002 0642 每个进程的权限掩码互不影响可以通过调用umask(掩码值)来设置当前进程的文件权限掩码。 文件权限这部分博主有过介绍这里不再赘述需要可移步Linux权限管理_linux没有root用户怎么办-CSDN博客 返回值成功时返回一个非负的文件描述符fd用于后续的文件操作失败时返回 - 1并设置errno变量来指示错误原因。 系统调用read 函数原型ssize_t read(int fd, void *buf, size_t count);参数解析 fd要读取的文件的描述符由open函数返回。buf用于存储读取数据的缓冲区是一个指向字符数组或其他数据类型的指针。count指定要读取的字节数。 返回值成功时返回实际读取的字节数。如果到达文件末尾返回 0。出错时返回 - 1并设置errno变量。 系统调用write 函数原型ssize_t write(int fd, const void *buf, size_t count);参数解析 fd要写入的文件的描述符。buf指向要写入数据的缓冲区的指针数据类型通常为const char *但也可以是其他数据类型取决于实际需求。count指定要写入的字节数。 返回值成功时返回实际写入的字节数。出错时返回 - 1并设置errno变量。 系统调用close 函数原型int close(int fd);参数解析fd为要关闭的文件的描述符。返回值成功时返回 0失败时返回 - 1并设置errno变量。关闭文件后相应的文件描述符将被释放可以被其他文件重新使用。同时系统会将与该文件描述符相关的内核缓冲区数据刷新到磁盘确保数据的完整性。 五、内核缓冲区、用户态标准I/O库缓冲区、缓冲区刷新 内核缓冲区 当使用系统调用函数如 open、read、write、close进行文件操作时涉及到内核缓冲区。在这种情况下系统调用close 函数会刷新内核缓冲区close 函数在关闭文件描述符时会确保将内核缓冲区中与该文件描述符相关的所有待写入数据刷新到磁盘。这是为了保证数据的完整性避免数据丢失。例如当你使用 write 函数将数据写入文件时数据可能只是被暂时存储在内核缓冲区中而不是立即写入磁盘。当调用 close 函数关闭文件描述符时内核会将这些缓冲的数据写入磁盘。 内核缓冲区的刷新机制 延迟写入策略内核为了提高文件 I/O 性能采用延迟写入策略。当应用程序调用 write 系统调用将数据写入文件时数据并不会立即被写入磁盘而是先被复制到内核缓冲区。内核会在合适的时机如缓冲区满、达到一定时间间隔、系统空闲等将缓冲区中的数据批量写入磁盘这样可以减少磁盘的 I/O 次数提高整体性能。数据一致性保证内核会确保在某些关键操作如关闭文件close、系统关机等时将内核缓冲区中的数据刷新到磁盘以保证数据的一致性和持久性。 刷新方式 close函数当调用 close 函数关闭文件描述符fd时内核会检查该文件描述符对应的内核缓冲区将其中尚未写入磁盘的数据刷新到磁盘然后释放相关的内核资源。fsync函数fsync 函数会强制将指定文件描述符fd对应的文件的所有数据和元数据如文件权限、修改时间等从内核缓冲区同步到磁盘。它会阻塞调用进程直到数据完全写入磁盘确保数据的持久性。fdatasync函数fdatasync 函数与 fsync 类似但它只保证文件的数据部分被写入磁盘而不强制刷新文件的元数据因此在性能上可能会比 fsync 稍好。 写入流程打开文件 -- 写入数据 -- 刷新内核缓冲区(可选) -- 关闭文件 用户态标准I/O库缓冲区 当使用标准 I/O 库函数如 fopen、fread、fwrite、fclose进行文件操作时会使用用户态的标准 I/O 库缓冲区。在这种情况下close 函数不会刷新标准 I/O 库缓冲区close 函数是系统调用它只能处理文件描述符相关的内核缓冲区而无法直接操作标准 I/O 库缓冲区。如果你使用 fopen 打开文件然后使用 fwrite 写入数据数据会先被存储在标准 I/O 库缓冲区中。如果想要确保数据被写入磁盘需要使用 fflush 函数刷新标准 I/O 库缓冲区或者使用 fclose 函数因为 fclose 函数在关闭文件流时会自动刷新标准 I/O 库缓冲区。 标准I/O库缓冲区的刷新机制 缓冲类型标准 I/O 库提供了三种缓冲类型分别是全缓冲、行缓冲和无缓冲。 全缓冲当缓冲区满时才会将数据刷新到内核缓冲区。通常用于对磁盘文件的操作。行缓冲当遇到换行符 \n 或者缓冲区满时将数据刷新到内核缓冲区。标准输入和标准输出默认采用行缓冲。无缓冲数据会立即被写入内核缓冲区不会进行缓冲。标准错误输出通常采用无缓冲。 自动刷新在某些情况下标准 I/O 库会自动刷新缓冲区如程序正常结束时。 刷新方式 fflush 函数fflush 函数用于将指定 FILE 指针对应的标准 I/O 库缓冲区中的数据刷新到内核缓冲区。如果传入 NULL 作为参数则会刷新所有打开的输出流的缓冲区。fclose 函数调用 fclose 函数关闭文件流时它会自动调用 fflush 函数将标准 I/O 库缓冲区中的数据刷新到内核缓冲区然后再关闭底层的文件描述符。 写入流程打开文件 -- 写入数据 -- 刷新标准I/O库缓冲区(可选) -- 关闭文件 六、文件描述符fd、重定向 通过上面对open的介绍知道open会返回一个int类型的值这个返回值称之为文件描述符File Descriptor即为fd。 在 Linux 系统里一切皆文件诸如普通文件、目录、设备文件像键盘、鼠标、磁盘等都可以通过文件描述符来进行操作。当进程打开或创建一个文件时内核会为该操作分配一个唯一的文件描述符进程后续对该文件的读写、定位等操作都通过这个文件描述符来进行。 Linux进程默认情况下会有三个缺省打开的文件描述符fd 标准输入stdin0对应的物理设备键盘标准输出stdout1对应的物理设备显示器标准错误stderr2对应的物理设备显示器通过文件操作和fd的结合使用从键盘输入到文件再从文件输入到显示器 #include stdio.h #include string.h #include sys/types.h #include sys/stat.h #include fcntl.hint main() {char buf[1024];ssize_t size read(0,buf,sizeof(buf));//input buf from fd:0if(size 0){buf[size] \0;write(1,buf,strlen(buf));write(2,buf,strlen(buf));}return 0; }当进程后续打开新的文件时系统会从最小的未使用的非负整数开始分配文件描述符。例如若 0、1、2 已被占用新打开文件时会分配 3 作为文件描述符。  那么为什么fd是int类型为什么fd是0、1、2、3...线性分配的? 当我们打开一个文件时OS在内存中创建相应的数据结构 file结构体 来描述这个打开的文件属性使用OS对打开的文件进行管理进程执行open调用就需要让进程和文件关联起来所以在每个进程的PCB中都有一个file*字段指向一张表file_struct文件描述符表表内存在一个指针数组每个元素都是一个指向进程打开的文件的指针所以文件描述符就是数组的下标一个进程只需要拿着文件描述符就可以找到对应的文件。  文件描述符的分配规则和重定向 通过两段代码看现象 clude stdio.h #include string.h #include sys/types.h #include sys/stat.h #include fcntl.h #include stdlib.hint main() {int fd open(myfile,O_CREAT | O_RDONLY, 0664);if(fd 0){perror(myfile open);exit(1);}printf(myfile fd:%d\n,fd);close(fd);return 0; } 输出 前面说过fd的0、1、2这三个是默认打开的如果新打开的文件fd就会分配当前没有被使用的最小的一个下标作为新的文件描述符。 #include stdio.h #include string.h #include sys/types.h #include sys/stat.h #include fcntl.h #include stdlib.hint main() {int fd1 open(myfile1,O_CREAT | O_RDWR, 0664);close(1);int fd2 open(myfile2,O_CREAT | O_RDWR, 0664);if(fd1 0 fd2 0){perror(myfile1 or myfile2 open);exit(1);}printf(myfile1 fd:%d\n,fd1);printf(myfile2 fd:%d\n,fd2);fflush(stdout);close(fd1);close(fd2);return 0; }close(1)后fd为1的输出流被关闭了所以当前没有被使用的最小下标就是1所以新打开的myfile2分配的fd就是1因为输出流被关闭了所以printf语句没有输出到显示器上而是输出到当前fd为1的myfile2通过cat就能发现输出内容。 注意这里需要调用fflush(stdout)刷新一下缓存区才能将内容重定向到myfile2中因为 printf 是标准 I/O 库函数它使用标准 I/O 缓冲区。在标准输出未重定向时标准输出通常采用行缓冲模式即遇到换行符 \n 时会自动刷新缓冲区。但当标准输出被重定向到文件时标准 I/O 库会将缓冲模式切换为全缓冲模式此时遇到换行符 \n 不会自动刷新缓冲区只有当缓冲区满或者手动调用 fflush 函数时缓冲区中的数据才会被写入文件。   原本应该输出到显示器的内容被输出到其他文件中fd1指向其他file结构体这样的现象就叫做输出重定向。常见的输出重定向有shell的 。 可以通过系统调用 dup2 来完成fd的指向完成重定向操作。 函数原型 #include unistd.h int dup2(int oldfd, int newfd); 参数 oldfd旧的文件描述符也就是要被复制的文件描述符。它必须是一个已经打开的有效的文件描述符。newfd新的文件描述符复制操作将把 oldfd 所指向的文件对象关联到 newfd 上。如果 newfd 已经打开那么在复制之前会先关闭 newfd。 返回值 成功返回新的文件描述符 newfd。失败返回 -1并设置 errno 来指示具体的错误原因。常见的错误包括 oldfd 无效、newfd 是一个无效的文件描述符编号等。 功能 dup2 函数的主要功能是将 oldfd 对应的文件描述符复制到 newfd 上。复制完成后oldfd 和 newfd 都会指向同一个文件对象它们共享文件的偏移量、文件状态标志等信息。也就是说对 oldfd 或 newfd 进行的读写操作都会影响同一个文件。 使用场景 输入输出重定向在 shell 脚本中经常会使用 dup2 来实现输入输出的重定向。例如将标准输出重定向到一个文件这样程序的所有输出都会被写入该文件而不是显示在终端上。多进程编程在创建子进程时子进程可以使用 dup2 来复制父进程的文件描述符从而实现父子进程之间共享文件资源。 示例 #include stdio.h #include unistd.h #include fcntl.h #include sys/types.h #include stdlib.hint main() {int fd open(myfile,O_CREAT | O_RDWR, 0664);if(fd 0){perror(myfile open);exit(1);}if(dup2(fd,1) -1){perror(dup2);close(fd);exit(1);}printf(This output will be redirected to the file\n);close(fd);return 0; }七、file结构体 上面谈到的file结构体是一个关键的数据结构它用于表示一个打开的文件。当进程打开一个文件时内核会为这个打开的文件实例创建一个 file 结构体通过该结构体对文件进行管理和操作。file 结构体定义在内核源码的 linux/fs.h 头文件中其定义较为复杂下面给出一个简化版本展示一些主要成员 struct file {union {struct llist_node fu_llist;struct rcu_head fu_rcuhead;} f_u;struct path f_path;struct inode *f_inode; /* cached value */const struct file_operations *f_op;/** Protects f_ep_links, f_flags.* Must not be taken from IRQ context.*/spinlock_t f_lock;atomic_long_t f_count;unsigned int f_flags;fmode_t f_mode;struct mutex f_pos_lock;loff_t f_pos;struct fown_struct f_owner;const struct cred *f_cred;struct file_ra_state f_ra;u64 f_version; #ifdef CONFIG_SECURITYvoid *f_security; #endif/* needed for tty driver, and maybe others */void *private_data;#ifdef CONFIG_EPOLL/* Used by fs/eventpoll.c to link all the hooks to this file */struct list_head f_ep_links;struct list_head f_tfile_llink; #endifstruct address_space *f_mapping; }; 1. 文件路径和索引节点信息 struct path f_path包含文件的挂载点和目录项信息用于确定文件在文件系统中的位置。struct inode *f_inode指向文件对应的 inode 结构体。inode 存储了文件的元数据如文件大小、权限、创建时间等以及文件数据块在磁盘上的存储位置信息。 2. 文件操作函数集 const struct file_operations *f_op指向一个 file_operations 结构体该结构体包含了一系列用于操作文件的函数指针如 read、write、open、release 等。不同类型的文件如普通文件、字符设备文件、块设备文件会有不同的 file_operations 实现通过 f_op 可以调用相应的操作函数来完成文件的读写等操作。 3. 文件状态和标志 unsigned int f_flags存储文件的打开标志如 O_RDONLY只读、O_WRONLY只写、O_RDWR读写等这些标志在调用 open 系统调用时指定。fmode_t f_mode表示文件的访问模式如读、写、执行等权限信息。 4. 文件偏移量 loff_t f_pos记录文件当前的读写位置即文件偏移量。每次进行读写操作时会根据操作的字节数更新这个偏移量。 5. 引用计数 atomic_long_t f_count用于记录对该 file 结构体的引用计数。当有新的文件描述符指向这个 file 结构体时引用计数加 1当关闭文件描述符时引用计数减 1。当引用计数为 0 时内核会释放该 file 结构体。 6.file 结构体的作用 管理打开的文件内核通过 file 结构体对每个打开的文件进行管理记录文件的状态、位置、操作函数等信息方便进行文件的读写、定位等操作。实现文件共享多个文件描述符可以指向同一个 file 结构体从而实现文件的共享访问。不同的进程或同一个进程的不同文件描述符可以共享同一个 file 结构体它们共享文件的偏移量和状态信息。抽象文件操作file 结构体中的 f_op 成员提供了统一的文件操作接口使得内核可以以相同的方式处理不同类型的文件提高了系统的可扩展性和兼容性。 7.相关操作 创建 file 结构体当进程调用 open 系统调用打开一个文件时内核会分配一个新的 file 结构体并初始化其各个成员。释放 file 结构体当所有引用该 file 结构体的文件描述符都被关闭后内核会释放该 file 结构体回收相关资源。文件操作通过 file 结构体中的 f_op 成员调用相应的操作函数实现文件的读写、定位等操作。 8.指向问题多个进程或者同一个进程里的多个文件描述符指向同一个文件时是否需要创建多个 file 结构体 同一个进程内多个文件描述符指向同一文件 使用 dup 或 dup2 复制文件描述符当在同一个进程里使用 dup 或者 dup2 系统调用复制文件描述符时不会创建新的 file 结构体。这两个系统调用的作用是复制已有的文件描述符让新的文件描述符和原文件描述符指向同一个 file 结构体。多个文件描述符共享同一个 file 结构体意味着它们共享文件的偏移量、状态标志等信息。例如若一个文件描述符改变了文件的偏移量其他指向同一 file 结构体的文件描述符也会受到影响。多次调用 open 打开同一文件如果在同一个进程里多次调用 open 函数打开同一个文件每次调用都会创建一个新的 file 结构体。这是因为每次 open 调用都被视为一个独立的打开操作每个 file 结构体有自己独立的文件偏移量和状态标志。不同的 file 结构体虽然对应同一个文件即共享同一个 inode 结构体但它们之间的操作是相互独立的。 不同进程指向同一文件 父子进程通过 fork 继承文件描述符当父进程打开一个文件后调用 fork 创建子进程子进程会继承父进程的文件描述符。这些继承的文件描述符会指向和父进程相同的 file 结构体。也就是说父子进程共享同一个 file 结构体它们对文件的操作会相互影响例如文件偏移量的改变会被双方感知。不同进程分别调用 open 打开同一文件如果不同的进程分别调用 open 函数打开同一个文件每个进程都会创建自己的 file 结构体。不同进程的 file 结构体是相互独立的它们有各自的文件偏移量和状态标志彼此之间的操作不会直接相互影响。 9.重新理解“一切皆文件” 在Linux中进程、磁盘、显示器、键盘等都被抽象成了文件可以使用访问文件的方法访问它们获取信息这样做的好处就是开发者只需要一套API和开发工具即可调取Linux系统中绝大部分的资源。举个简单例子Linux中几乎所有读操作读文件读系统状态读PIPE都可以用read函数进行几乎所有更改操作更改文件、更改系统参数、写PIPE都可以用write函数来进行。 在结构体struct file中的 f_op 指针指向一个 file_operations 结构体这个结构体中的成员除了 struct module *owner 其余都是函数指针。 struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*iterate) (struct file *, struct dir_context *);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **, void **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMUunsigned (*mmap_capabilities)(struct file *); #endif }; file_operator 就是把系统调用和驱动程序关联起来的关键数据结构这个结构的每一个成员都对应着一个系统调用读取 file_operator 中相应的函数指针然后把控制权交给函数从而完成Linux设备驱动程序的工作。每个设备都有自己的read、write但一定是对应着不同的操作方法实现不同但通过struct file下 file_operation 中的各种函数回调让开发者只用file便可以调取Linux系统中绝大部分的资源这就是“一切皆文件”的核心。 八、FILE与fd 重新梳理一下FILE和fd的区别和关系​​​​​​​ 抽象层次的区别 fd文件描述符属于操作系统内核层面的概念。它是一个非负整数是内核为了管理进程打开的文件而分配的索引。进程通过文件描述符与内核中的文件对象交互是一种底层的文件操作方式。FILE是标准 C 库层面的抽象。FILE 是一个结构体类型它封装了文件描述、系统调用以及其他与文件操作相关的信息如缓冲区状态、错误标志等。 接口的区别 fd使用系统调用函数进行操作例如 open、read、write、close 等。这些系统调用直接与内核交互绕过了标准 C 库的缓冲区数据直接在用户空间和内核空间之间传输。FILE使用标准 C 库函数进行操作如 fopen、fread、fwrite、fclose 等。这些函数在内部会调用相应的系统调用同时还处理了缓冲区管理、错误检查等额外的工作。 缓冲区管理 fd不涉及标准 C 库的缓冲区。使用 read 和 write 系统调用时数据直接在用户空间和内核缓冲区之间复制。内核会根据自身的策略如延迟写入来决定何时将内核缓冲区中的数据写入磁盘。FILEFILE 结构体管理着标准 C 库的缓冲区有全缓冲、行缓冲和无缓冲三种模式。全缓冲模式下缓冲区满时才会将数据刷新到内核缓冲区行缓冲模式下遇到换行符或缓冲区满时刷新无缓冲模式下数据立即写入内核缓冲区比如标准错误。可以使用 fflush 函数手动刷新缓冲区。 FILE和fd相互转换 从 fd 到 FILE可以使用 fdopen 函数将一个文件描述符转换为 FILE 指针。这样就可以使用标准 C 库函数对该文件进行操作。从 FILE 到 fd可以使用 fileno 函数从 FILE 指针获取对应的文件描述符。这在需要使用系统调用对 FILE 指针指向的文件进行操作时非常有用。 FILE 结构体在内部会持有一个文件描述符通过这个文件描述符与内核中的文件对象进行交互。也就是说标准 C 库函数在实现文件操作时最终还是会调用系统调用借助文件描述符来完成实际的文件读写等操作。 看如下代码 #include stdio.h #include string.h #include unistd.hint main() {const char *s1 Hello printf\n;const char *s2 Hello fwrite\n;const char *s3 Hello write\n;printf(%s,s1);fwrite(s2,strlen(s2),1,stdout);write(1,s3,strlen(s3));fork();return 0; }为什么同一个代码输出到显示器和输出到文件的结构不同呢 一般C库函数写入文件时是全缓冲的而写入显示器是行缓冲。printf fwrite库函数会自带缓冲区当发生重定向到普通文件时数据的缓冲方式由行缓冲变成了全缓冲所以不会遇到\n就刷新了而我们放在缓冲区中的数据就不会被立即刷新甚至fork之后也没有刷新缓冲区fork的时候父子数据会发生写时拷贝子进程也有一份同样的数据在父进程的缓冲区的数据在子进程也有一份进程退出之后会统一刷新父子进程的缓冲区的数据写入所以同样的一份数据随即产生两份数据。然而write 没有变化说明没有所谓printf和write的缓冲。 所以printf和fwrite 库函数会自带缓冲区而 write 系统调用没有带缓冲区。另外我们这 里所说的缓冲区都是用户级缓冲区标准库I/O缓冲区。为了提升整机性能OS也会提供相关内核级缓冲区。 那这个缓冲区谁提供呢? printf和fwrite是库函数write是系统调用库函数在系统调用的“上层”是对系统调用的“封装”但是 write 没有缓冲区而 printf和fwrite 有足以说明该缓冲区是二次加上的又因为是C所以由C标准库提供。 glibc 中的 FILE 结构体 struct _IO_FILE {int _flags; /* 文件状态标志如是否为只读、是否已到达文件末尾等 */char *_IO_read_ptr; /* 当前读指针位置 */char *_IO_read_end; /* 读缓冲区的结束位置 */char *_IO_read_base; /* 读缓冲区的起始位置 */char *_IO_write_base; /* 写缓冲区的起始位置 */char *_IO_write_ptr; /* 当前写指针位置 */char *_IO_write_end; /* 写缓冲区的结束位置 */char *_IO_buf_base; /* 缓冲区的起始位置 */char *_IO_buf_end; /* 缓冲区的结束位置 */int _fileno; /* 文件描述符 *//* 其他成员用于处理错误状态、锁机制、流的方向等 */ };typedef struct _IO_FILE FILE; 可以看出FILE结构体封装了fd和缓冲区。 模拟简单封装FILE和操作函数 //my_stdio.h#pragma once#define SIZE 2048 #define FLUSH_NONE 0 #define FLUSH_LINE 1 #define FLUSH_FULL 2typedef struct IO_FILE{int flag;int fileno;char outbuffer[SIZE];int cap;int size; }myFILE;myFILE *myfopen(const char *filename,const char *mode); int myfwrite(const void *ptr,int num,myFILE *stream); void myfflush(myFILE *stream); void myclose(myFILE *stream);//my_stdio.c#include my_stdio.h #include string.h #include stdlib.h #include unistd.h #include sys/stat.h #include sys/types.h #include fcntl.hmyFILE*myfopen(const char *filename, const char *mode){int fd -1;if(strcmp(mode,r) 0)fd open(filename,O_RDONLY);else if(strcmp(mode,w) 0)fd open(filename,O_CREAT | O_WRONLY | O_TRUNC, 0666);else if(strcmp(mode,a) 0)fd open(filename,O_CREAT | O_WRONLY | O_APPEND, 0666);else{perror(myfopen mode);exit(1);}if(fd 0) return NULL;myFILE *mf (myFILE*)malloc(sizeof(myFILE));if(!mf){close(fd);return NULL;}mf-fileno fd;mf-flag FLUSH_LINE;mf-size 0;mf-cap SIZE;return mf; }int myfwrite(const void *ptr,int num,myFILE *stream){memcpy(stream-outbufferstream-size,ptr,num);stream-size num;//flushif(stream-flag FLUSH_LINE stream-size 0 stream-outbuffer[stream-size - 1] \n)myfflush(stream);return num; }void myfflush(myFILE *stream){if(stream-size 0){write(stream-fileno,stream-outbuffer,stream-size);fsync(stream-fileno);stream-size 0;} }void myclose(myFILE *stream){if(stream-size 0)myfflush(stream);close(stream-fileno); } //main.c#include my_stdio.h #include stdio.h #include string.h #include unistd.hint main() {myFILE *fp myfopen(myfile,w);if(fp NULL)return 1;int i;for(i 1; i 10; i){printf(write %d\n,i);char buffer[64];snprintf(buffer,sizeof(buffer),hello, num is %d,i);myfwrite(buffer,strlen(buffer),fp);myfflush(fp);sleep(1);}myclose(fp);return 0; }
http://www.w-s-a.com/news/620306/

相关文章:

  • 湖南省建设人力资源网站wordpress主机pfthost
  • 淮安软件园哪家做网站各网站特点
  • 网站长尾关键词排名软件重庆荣昌网站建设
  • 建个商城网站多少钱茂名专业网站建设
  • 开通公司网站免费的网站app下载
  • 跨境电商网站模板wordpress壁纸
  • 国内做网站网站代理电子商务网站建设与维护概述
  • 如何做地方网站推广沈阳网势科技有限公司
  • 哈尔滨网站优化技术涵江网站建设
  • 做网站搞笑口号wordpress全屏动画
  • 怎么可以建网站小程序代理项目
  • 怎样做软件网站哪个网站用帝国cms做的
  • 网站开发编程的工作方法wordpress dux-plus
  • 廊坊电子商务网站建设公司网站进不去qq空间
  • 南宁网站推广费用创意网页设计素材模板
  • 深圳技术支持 骏域网站建设wordpress 酒主题
  • 东莞网站建设+旅游网站改版数据来源表改怎么做
  • 手机端做的优秀的网站设计企业做网站大概多少钱
  • 优化网站使用体验手机网站解析域名
  • 网站制作 商务做网站的软件名字全拼
  • 阿里巴巴网官方网站温州网站建设设计
  • 传奇购买域名做网站国外网站设计 网址
  • 西安凤城二路网站建设seo网站是什么
  • 网站后台如何更换在线qq咨询代码在线种子资源网
  • 东莞网站优化制作免费中文wordpress主题下载
  • 东莞建筑设计院排名网络优化论文
  • 做牙工作网站郑州前端开发培训机构
  • 温州专业建站网站制作的管理
  • 公司网站开发策划书有没有专门做教程的网站
  • 江苏省工程建设信息网站一天赚1000块钱的游戏