C库函数

        Linux的系统I/O函数(read、write、open、close和 lseek等)与C语言的C库函数(libc.so库文件中)都是相对应的,它们都是动态库函数。如下图所示,C库函数有fopen、fclose、fwrite、fread和fseek等。这些C库函数都封装在libc.so库文件中,其中的fopen函数用于打开一个文件,其返回值为FILE *类型(指向FILE类型的一个指针),FILE类型为一个结构体,用于描述对打开的文件的一些操作,对于除了fopen以外的C库函数都会通过这个FILE *类型指针对打开的文件进行操作。

FILE类型为一个结构体,包括三个部分:文件描述符、文件读写指针和I/O缓冲区。

文件描述符(整型)用于索引到对应的磁盘文件,类似于FCB(文件控制块)和索引结点,包含了该文件在磁盘上的位置信息。对于每一个进程打开的所有文件在其PCB中都有记录相应的文件描述符,如0代表标准输入,其宏定义为STDIN_FILENO(#define STDIN_FILENO 0)。所有执行I/O操作的系统调用都以文件描述符,即一个非负整数来指代所打开的文件。文件描述符可以用来表示所有类型的已打开文件。同时,多个文件描述符可以指向同一个打开文件,因为有在不同进程中打开同一个文件的需求。

文件描述符是一个整型数,文件描述符表是一个整型数组,而在PCB中有一个指针,指向文件描述符表的首地址,因此根据PCB就可以找到对应的文件描述符,而每一个文件描述符都对应一个FILE *的指针,即可以根据文件描述符进一步找到FILE结构体,该结构体保存了所打开文件的属性信息(文件大小、I/O缓冲、读写指针、文件打开次数等等),而这样的结构体每个文件都只有一份,供所有打开该文件的进程共享,而这些进程所不同的只是文件描述符不一样。FILE结构体的内容只有一份。FILE结构体类似inode,而文件描述符类似简化的FCB。

每一个文件只有一个文件读写指针(读写文件过程中指针的实际位置),在文件读写时要时刻注意当前文件指针的位置。文件读写指针指向将要写入或读出的下一个地址。如果一个进程对文件正在进行写入,此时另一个进程要读取该文件的数据,需要利用fseek函数将文件读写指针置于文件的起始位置才能读取数据。

系统调用指操作系统提供给用户程序调用的一组接口(接口函数)来获得内核提供的服务。在实际中程序员使用的通常不是系统调用,而应用程序接口API,也称为系统调用编程接口,接口函数可能要一个或者几个系统调用才能完成函数功能,此函数通过c库(libc)实现,如fread,fopen等。

I/O缓冲区。每一个C库函数在对文件操作时都会有对应的I/O缓冲区,I/O缓冲区属于内存的一部分,位于用户空间,默认大小为8KB,即8192个Bytes。

以fgetc / fputc函数为例,当用户程序第一次调用fgetc 读一个字节时,fgetc函数可能通过系统调用进入内核读1K字节到I/O缓冲区中,然后返回I/O缓冲区中的第一个字节给用户,把读写位置指向I/O缓冲区中的第二个字符,以后用户再调fgetc ,就直接从I/O缓冲区中读取,而不需要进内核了,当用户把这1K字节都读完之后,再次调用fgetc时,fgetc 函数会再次进入内核读1K字节到I/O缓冲区中。C标准库之所以会从内核预读一些数据放 在I/O缓冲区中,是希望用户程序随后要用到这些数据,C标准库的I/O缓冲区也在用户空间,直接从用户空间读取数据比进内核读数据要快得多。另一方面,用户程序调用fputc通常只是写到I/O缓冲区中,这样fputc 函数可以很快地返回,如果I/O缓冲区写满了,fputc 就通过系统调用把I/O缓冲区中的数据传给内核,内核最终把数据写回磁盘或设备。有时候用户程序希望把I/O缓冲区中的数据立刻给内核,让内核写回设备或磁盘,这称为Flush操作,对应的库函数是fflush,fclose函数在关闭文件之前也会做Flush操作。

由上可以看出,fgetc和fputc库函数在完成工作的过程中可能需要几个系统调用,如将I/O缓冲区的数据写入内核缓冲,将内核缓冲数据刷到磁盘上等。因此,通过增设I/O缓冲区和内核缓冲可以减少进入内核的次数和对磁盘的操作次数(这些都需要系统调用来完成),从而提高了读写效率。相对于内存,机械硬盘的读写速度很慢。机械硬盘的读写寻道时间ms级,而内存的读写速度是ns级别。另外,从用户空间(I/O缓冲区)直接读写数据也会比进入内核(系统调用)要快很多。

Flush操作。指把I/O缓冲区的数据立即传送给内核,然后刷到磁盘上,分为强制性和非强制性两种。C库函数fflush是强制性把I/O缓冲区的数据立即写给内核的缓冲区;C库函数fsync是强制性把内核缓冲区的数据立即刷到磁盘上。main函数的return和调用main函数中的exit(退出当前进程),也会把缓冲区的数据立即写到磁盘上。I/O缓冲区已经写满或者关闭文件时,将会自动将其数据写到磁盘上。

内核缓冲区。人生三大错觉之中的一个:在调用函数write()时,我们觉得该函数一旦返回,数据便已经写到了文件里,可是这样的概念仅仅是宏观上的。实际上,操作系统实现某些文件I/O时(如磁盘文件),为了保证I/O的效率,在内核一般会用到一片专门的区域(内存或独立的I/O地址空间)作为I/O数据缓冲区.它用在输入输出设备和CPU之间,用来缓存数据,使得低速的设备和快速的CPU可以协调工作避免低速的输入输出设备长时间占用CPU,降低系统调用,提高了CPU的工作效率。

Linux系统I/O函数是没有I/O缓冲区的,其缓存是由用户提供的。C库函数都有对应的I/O缓冲区8KB(8196Bytes)。注意:内核缓冲和I/O缓冲的作用不同

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/385413.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C库函数与Linux系统函数之间的关系

由上小节知道,C库函数是借助FILE类型的结构体来对文件进行操作的,其本身只是在用户空间(I/O缓冲区)进行读写操作,而数据在内核与用户空间之间的传递、以及将内核与I/O设备之间的数据传递都是该C库函数进行一系列的系统…

open函数和errno全局变量

(1)open函数 man man 查看man文档的首页 其中DESCRIPTION部分描述了man文档的每一章的章节内容 第2章System calls为系统调用,即Liunx系统函数。 man 2 open 查看第二章的open函数的详细帮助文件。 open函数用于打开一个已经的文件或者创…

open函数和close函数的使用

学习几个常用的Linux系统I/O函数&#xff1a;open、close、write、read和lseek。注意&#xff0c;系统调用函数必须都考虑返回值。 &#xff08;1&#xff09;open函数的使用 首先&#xff0c;需要包含三个头文件&#xff1a;<sys/types.h> <sys/stat.h> <…

1091. Acute Stroke (30)

One important factor to identify acute stroke (急性脑卒中) is the volume of the stroke core. Given the results of image analysis in which the core regions are identified in each MRI slice, your job is to calculate the volume of the stroke core. Input Speci…

stat函数(stat、fstat、lstat)

#include <sys/types.h> #include <sys/stat.h> #include <unistd.h> //需包含头文件 有如下三个函数的函数原型&#xff1a; int stat(const char *path, struct stat *buf); 第一个形参&#xff1a;指出文件&#xff08;文件路径&#xff09;&…

【Leetcode | 235】 235. 二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表示为一个结点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是它自己…

CPU和MMU(内存管理单元)

CPU的架构&#xff1a;要求能够理解从源程序到微指令的整个经历过程&#xff1a;存储器的层次结构&#xff08;网络资源下载到硬盘、磁盘缓存、内存、Cache、寄存器&#xff09;&#xff1b;CPU的四大部分&#xff1a;ALU、CU、中断系统和寄存器&#xff1b;程序执行的整个过程…

【C++ Primer | 09】容器适配器

一、stack s.push(): 向栈内压入一个成员&#xff1b; s.pop(): 从栈顶弹出一个成员&#xff1b; s.empty(): 如果栈为空返回true&#xff0c;否则返回false&#xff1b; s.top(): 返回栈顶&#xff0c;但不删除成员&#xff1b; s.size(): 返回栈内元素…

进程控制块PCB(进程描述符)

&#xff08;1&#xff09;PCB 每个进程在内核中都有一个进程控制块&#xff08;PCB&#xff09;来维护进程相关的信息&#xff0c;Linux内核的进程控制块是task_struct结构体。grep -r “task_struct” / 可以查找根目录下&#xff0c;包含task_struct的文件文件。或者 find…

【C++ Primer | 19】控制内存分配

1. 测试代码&#xff1a; #include <iostream> #include <new> #include <cstring> #include <cstdlib> using namespace std;void* operator new(size_t size) {cout << "global Override operator new" << endl;if (void* p…

【第15章】虚函数

一、为什么基类中的析构函数要声明为虚析构函数&#xff1f; 直接的讲&#xff0c;C中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说&#xff0c;如果派生类中申请了内存空间&#xff0c;并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数&am…

【C++ Primer | 08】IO库

一、istringstream类 描述&#xff1a;从流中提取数据&#xff0c;支持 >> 操作 这里字符串可以包括多个单词&#xff0c;单词之间使用空格分开 #include <iostream> #include <sstream> using namespace std; int main() {istringstream istr(&quo…

EXEC函数族的一般规律

事实上&#xff0c;只有execve是真正的系统调用&#xff0c;其它五个函数最终都调用execve&#xff0c;所以execve在man手册第2节&#xff0c;其它函数在man手册第3节。这些函数之间的关系如下图所示。

进程间通信的方法

Linux环境下&#xff0c;进程地址空间相互独立&#xff0c;每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到&#xff0c;所以进程和进程之间不能相互访问&#xff0c;要交换数据必须通过内核&#xff0c;在内核中开辟一块缓冲区&#xff0c;进…

pipe函数

#include <unistd.h> int pipe(int pipefd[2]); 作用&#xff1a;创建管道 成功&#xff1a;0&#xff1b;失败&#xff1a;-1&#xff0c;设置errno 函数调用成功返回r/w两个文件描述符。无需open&#xff0c;但需手动close。规定&#xff1a;fd[0] …

mmap内存映射、system V共享内存和Posix共享内存

linux内核支持多种共享内存方式&#xff0c;如mmap内存映射&#xff0c;Posix共享内存&#xff0c;以system V共享内存。当内核空间和用户空间存在大量数据交互时&#xff0c;共享内存映射就成了这种情况下的不二选择。它能够最大限度的降低内核空间和用户空间之间的数据拷贝&a…

匿名映射

通过使用我们发现&#xff0c;使用映射区来完成文件读写操作十分方便&#xff0c;父子进程间通信也较容易。但缺陷是&#xff0c;每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件&#xff0c;创建好了再unlink、close掉&#xff0c;比较麻烦。…

信号的产生和状态

信号的产生&#xff1a;1.按键产生&#xff0c;如&#xff1a;Ctrlc&#xff08;内核向进程发送信号&#xff0c;杀死该进程&#xff09;、Ctrlz、Ctrl\&#xff1b;2.系统调用产生&#xff0c;如&#xff1a;kill、raise、abort&#xff1b;3.软件条件产生&#xff0c;如&…

【C++ Priemr | 15】虚函数常见问题

1. 在成员函数中调用虚函数&#xff1a; #include <iostream> using namespace std; class CBase { public:void func1(){func2();}virtual void func2() { cout << "CBase::func2()" << endl; } }; class CDerived : public CBase { public:virt…

965. 单值二叉树

如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。 只有给定的树是单值二叉树时&#xff0c;才返回 true&#xff1b;否则返回 false。 示例 1&#xff1a; 输入&#xff1a;[1,1,1,1,1,null,1] 输出&#xff1a;true示例 2&#xff1a; 输入&#…