🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️
本博客致力于分享知识,欢迎大家共同学习和交流。
当我们在程序中调用C标准库函数,比如printf,send,cout,会传递相应的参数给该函数。这些函数虽然具体功能不同,但是底层调用的都是操作系统函数write。C标准库函数的内部实现通常会调用底层的系统函数来完成所需的功能。举例来说,printf函数内部可能会调用write系统函数来将格式化的输出写入标准输出设备。
系统函数(例如write)是由操作系统提供的函数,用于执行底层的系统操作。这些操作可能涉及到与设备、文件系统、网络等底层资源的交互。
系统函数通常会调用底层的系统调用来实际执行操作。系统调用是用户程序与操作系统之间的接口,允许用户程序请求操作系统提供的服务,比如打开文件、读写数据、创建进程等。
为什么要学习操作系统函数
这里我们需要了解一下操作系统的架构。
简单来说,操作系统可以分为三部分,应用层(用户的操作),系统层,硬件层。
应用层:应用层是用户直接与操作系统交互的层次。这里包括各种应用程序和用户界面,用户通过这些程序来完成各种任务,如文字处理、图形设计、网络浏览等。应用层的程序运行在用户空间,通常不直接访问硬件资源,而是通过系统调用请求操作系统提供的服务。
系统层:系统层是操作系统的核心部分,也称为内核。系统层负责管理和控制计算机的硬件资源,为应用程序提供各种服务和功能。这包括进程管理、内存管理、文件系统、设备驱动程序、网络协议栈等。系统层包含了操作系统的大部分核心代码,通常运行在特权模式下,可以直接访问硬件资源。
硬件层:硬件层是实际的物理硬件设备,包括CPU、内存、磁盘、网络接口等。操作系统通过设备驱动程序与硬件交互,控制和管理这些硬件资源的访问和使用。
用户在应用层做出操作,与操作系统进行交互,系统层处理交互和请求,来调用硬件层的硬件实现对应的功能。
调用C标准库函数和系统函数的区别
举个例子:
当我们在应用程序中调用 printf 函数,并传递需要打印的字符串和格式化参数。
printf 函数内部会调用系统层的函数来完成打印操作,通常会调用 write 系统调用来将字符串写入标准输出设备(比如终端)。系统层的内核会处理 write 系统调用。它会检查你的应用程序是否有权限执行该调用,然后将字符串从应用程序的内存复制到内核的内存。
当内核收到 write 系统调用后,它将字符串传递给设备驱动程序,设备驱动程序负责与实际的输出设备(比如显示器)进行通信。设备驱动程序会将字符串发送到硬件设备,最终在屏幕上显示出来。
当我们在应用程序层面执行某个操作,然后通过系统调用告知操作系统内核,最终由内核通过设备驱动程序调用硬件执行这个操作。
所以我们调用C标准库函数时需要应用层、系统层和硬件层之间互相交互。应用层负责调用函数,系统层负责处理系统调用和管理资源,硬件层负责实际的硬件操作。
为什么不直接调用系统层函数,例如write、read呢?省去了应用层调用系统层函数的操作,这是一种更底层、更直接的方式
C标准库函数(如stdio.h中的printf、scanf等)和Linux系统函数(如unistd.h中的read、write等)的区别。
实现方式:
C标准库函数通常由编译器的标准库提供,而Linux系统函数是由Linux操作系统提供的系统调用接口。
C标准库函数通常是用C语言编写的,而Linux系统函数是由操作系统内核实现的,通常是用C语言编写,但也可能包含汇编语言部分。
跨平台性:
C标准库函数是跨平台的,理论上在任何符合C标准的编译器和操作系统上都可以使用。
Linux系统函数是特定于Linux操作系统的,虽然某些函数在其他类Unix系统上也可能存在,但其行为和用法可能有所不同。
功能范围:
C标准库函数主要提供了一些常见的功能,如文件操作、字符串处理、内存管理等,但功能相对简单,适用于大多数通用编程任务。
Linux系统函数提供了更底层的系统调用接口,可以进行更底层、更高效的操作,如进程管理、文件系统操作、网络通信等,适用于系统编程和底层开发。
性能:
由于Linux系统函数是直接调用操作系统内核提供的接口,通常比C标准库函数更高效,特别是在需要进行系统级操作时。
依赖关系:
C标准库函数通常不需要额外的依赖,因为它们通常是编译器提供的标准库的一部分。
Linux系统函数需要依赖于操作系统内核,因此在使用时需要确保在Linux系统上运行,并且可能需要特定的头文件和库。
调用系统函数的好处:
- 性能优化:直接调用系统层函数可以减少应用层与系统层之间的函数调用开销,从而提高程序的执行效率。在对性能要求较高的场景下,直接调用系统函数可能更合适。
- 功能定制:有时候,应用程序可能需要对系统调用进行更加灵活的控制,直接调用系统层函数可以绕过标准库函数对系统调用的封装,从而实现更加定制化的功能。
- 系统特性:某些系统功能可能只能通过系统调用来实现,无法通过标准库函数直接访问。在这种情况下,只能直接调用系统函数来实现所需的功能。
FILE结构体
当我们打开一个文件时,实际上会返回FILE*结构指针,FILE* fp=fopen(F_PATH,"r");
FILE文件流指针的结构体如图所示。
FILE结构指针包含三个信息,文件描述符,文件读写的光标位置f_pos,写多大的buffer。
文件描述符:指向真正的磁盘文件,在进行文件读写操作时候是先读写到缓冲区,然后再调用系统应用层API write函数进行写操作,write将文件内容写到内核缓冲区,然后再调用内核层API sys_write进行写操作。减少I/O,提高效率。
f_pos:光标位置,决定了文件被写入的位置
buffer缓冲区:在文件读写的时候,如果没有缓冲区,就会一个字节一个字节往文件里写,效率很低。文件写入是向磁盘的磁道里面写数据,同一时间可能有多个文件在读写,这样返回来回操作会浪费分多时间,效率不高。这时候就需要一个缓冲区,缓冲区的大小是8192B也就是8KB。写文件时,会先把数据存入缓冲区,然后分为两种模式向磁盘中写,一种模式是通过fflush函数把缓冲区里面的内容一次性写入到文件中;第二种方式是等到缓冲区8192B带下被填满后才会写入到文件中
C标准库函数是有缓冲区,当我们往磁盘上写入数据时,会先写入buffter缓冲区,然后buffer缓冲区会根据自己的刷新方式,一次性把数据送出去
刷新缓冲区:一般使用fflush()函数去刷新,当缓冲区满、程序的正常结束、以及fclose操作等都会刷新缓冲区。换行符\n 只能刷新终端文件的缓冲区
进程控制块
PCB(Process Control Block),进程控制块是操作系统中用于管理进程的数据结构。每个正在运行的进程都有一个对应的 PCB,PCB 中保存了与该进程相关的所有信息。PCB 是操作系统调度和管理进程所必需的信息的集合。
PCB 包含的信息通常包括以下内容:
- 进程状态信息:记录进程的当前状态,如就绪、运行、阻塞等。
- 程序计数器(PC):指向进程下一条要执行的指令的地址。
- 堆栈指针(SP):指向进程的当前堆栈顶部。
- 内存管理信息:包括进程的内存分配情况、页表等。
- 进程标识符:唯一标识一个进程的标识符,如进程 ID。
- 进程调度信息:包括进程的优先级、调度策略等。
- 打开文件表:记录进程打开的文件信息,如文件描述符、文件位置指针等。
- 父进程指针:指向创建该进程的父进程。
- 子进程列表:记录该进程创建的子进程。
- 信号处理器信息:记录进程注册的信号处理函数。
- 资源占用信息:记录进程使用的各种资源,如 CPU 时间、内存、I/O 设备等。
PCB 在操作系统中起到了关键的作用,它保存了进程的状态和上下文信息,使得操作系统可以在多个进程之间进行切换和调度。当一个进程被抢占或者暂停时,其状态和上下文信息都会保存在 PCB 中,等待操作系统恢复时再次加载。
和Windows一样,Linux下每一个进程也是分配4G的虚拟内存,但是不同的是0~3G是用户空间,3G~4G是内核空间。内核空间通过PCB管理进程,PCB中有一个files_struct结构体,就是当前这个应用所有的文件。
files_struct结构体
files_struct结构体是一个指针,指向了一个int数组,这个数组叫做文件描述符数组。文件描述符是操作系统中用于标识文件或其他I/O资源的抽象概念。
文件描述符的工作原理如下:
标准文件描述符:在Unix系统中,通常会预定义三个标准文件描述符:
标准输入(stdin):文件描述符为0,通常用于读取用户输入。
标准输出(stdout):文件描述符为1,通常用于输出程序结果。
标准错误(stderr):文件描述符为2,通常用于输出错误信息。
- 文件描述符分配:当应用程序打开一个文件或者创建一个新的I/O资源时,操作系统会分配一个文件描述符来标识这个资源。每个打开的文件或I/O资源都会被分配一个唯一的文件描述符。
- 使用文件描述符:应用程序可以通过文件描述符来对文件进行读写操作、控制文件状态等。例如,可以使用文件描述符调用系统调用
read
和write
来读写文件,或者调用close
来关闭文件。 - 限制和管理:操作系统会限制一个进程可以打开的文件描述符数量,这通常由系统配置或者进程的资源限制来确定。在Unix系统中,可以使用
ulimit
命令查看和设置文件描述符的限制。
文件描述符的使用对于操作系统提供的I/O功能至关重要。通过文件描述符,操作系统可以跟踪和管理进程对文件和其他I/O资源的访问,从而保证资源的正确使用和管理。