Linux系统编程之文件和I/O操作(三)

系统调用

什么是系统调用:
系统调用(System Call)是操作系统提供给应用程序的一种编程接口,用于访问底层操作系统的功能和服务。应用程序通过系统调用向操作系统发出请求,以执行特权操作或获取操作系统提供的服务。应用程序通常运行在用户空间,而操作系统运行在内核空间。用户空间和内核空间是处理器提供的不同的执行模式,用户空间只能访问受限资源和执行受限操作,而内核空间具有更高的权限和访问系统资源的能力。应用程序通过系统调用将执行权交给操作系统内核,在内核空间中执行相应的操作。系统调用允许应用程序执行以下操作,包括但不限于:

  1. 文件操作:打开、读取、写入、关闭文件等。
  2. 进程管理:创建、终止、等待进程,进程间通信等。
  3. 内存管理:分配、释放内存,修改内存保护等。
  4. 网络通信:建立网络连接,发送和接收数据等。
  5. 设备控制:访问设备驱动程序,读取和写入设备数据等。
  6. 时间和日期:获取系统时间和日期信息。
  7. 权限管理:获取和修改文件、进程等的权限。

不同的操作系统提供不同的系统调用接口和系统调用编号,应用程序需要使用特定操作系统的系统调用接口来与操作系统进行交互。通常,操作系统提供一组库函数,使得应用程序能够以更高级别的方式调用系统调用,而不需要直接操作底层系统调用接口。系统调用提供了应用程序与操作系统之间的界面,使得应用程序能够利用操作系统的功能和服务,例如访问硬件设备、进行文件操作、进行网络通信等,从而实现更复杂和强大的功能。
一个helloworld打印到屏幕上

C标准库文件I/O函数

open/close函数

open() 函数是在 POSIX 操作系统中用于打开文件的系统调用。它提供了访问文件的能力,可以用于创建新文件、打开现有文件或修改文件的访问权限。函数原型如下:

#include <fcntl.h>
int open(const char *path, int flags, mode_t mode);

参数说明:

  • path:要打开的文件路径。
  • flags:打开文件的标志,用于指定打开文件的方式和行为。例如,可以使用 O_RDONLY 以只读方式打开文件,O_WRONLY 以只写方式打开文件,或者 O_RDWR 以读写方式打开文件。还可以使用其他标志来指定文件创建、截断等行为。
  • mode:用于指定新文件的访问权限(仅在创建新文件时有效),通常使用八进制表示。例如,0644 表示用户具有读写权限,其他用户只有读权限。

返回值:

  • 如果成功,open() 函数返回一个文件描述符(file descriptor),该文件描述符用于后续对文件的读写操作。文件描述符是一个非负整数。如果发生错误,open() 函数返回 -1,并设置相应的错误码,可以使用 errno变量获取具体的错误信息。
  • open() 函数是一个底层的系统调用,通常会与其他文件操作函数(如 read()、write()、close() 等)一起使用,以对文件进行读写操作。具体使用方法和示例可参考操作系统或编程语言相关的文档或教程。

举例说明:
下面示例使用参数 O_RDONLY | O_CREAT | O_TRUNC 意思是只读O_RDONLY打开当前目录下 dict.cp 文件,若无则创建O_CREAT,最后将其截断O_TRUNC为0。如果读一个不存在的文件即去掉O_CREAT会返回fd=-1

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
	int fd;
	fd=open("./dict.cp",O_RDONLY | O_CREAT | O_TRUNC,0644);
	printf("fd=%d\n",fd);
	close(fd);
	return 0;
}

创建文件时,指定文件访问权限。权限同时受umask影响。文件权限=mode & ~mask。

open() 函数错误以及可能的原因:

  • 文件不存在(ENOENT):指定的文件路径不存在。请确保指定的文件路径正确,并且文件确实存在。
  • 权限被拒绝(EACCES):当前用户没有足够的权限来打开指定的文件。请检查文件的权限设置,并确保当前用户具有适当的权限。

read/write 函数

read()write() 函数是在 Linux/Unix 系统编程中用于进行文件读取和写入的函数。下面是它们的介绍和参数说明:

  1. read() 函数(读取函数):

    • 函数原型:ssize_t read(int fd, void *buf, size_t count);
    • 功能:从文件描述符 fd 指定的文件中读取数据,并将数据存储到 buf 指向的缓冲区中。
    • 参数:
      • fd:文件描述符,用于指定要读取的文件。
      • buf:存储读取数据的缓冲区的指针。
      • count:要读取的最大字节数。
    • 返回值:
      • 成功时,返回实际读取的字节数。
      • 若已到达文件末尾,返回 0。
      • 出错时,返回 -1,并设置相应的错误码。
  2. write() 函数(写入函数):

    • 函数原型:ssize_t write(int fd, const void *buf, size_t count);
    • 功能:将位于 buf 指向的缓冲区中的数据写入到文件描述符 fd 指定的文件中。
    • 参数:
      • fd:文件描述符,用于指定要写入的文件。
      • buf:要写入的数据的缓冲区的指针。
      • count:要写入的字节数。
    • 返回值:
      • 成功时,返回实际写入的字节数。
      • 出错时,返回 -1,并设置相应的错误码。

这些函数通常与文件描述符配合使用,可以用于对文件进行读取和写入操作。需要注意的是,read()write() 是阻塞式的函数,如果数据无法立即读取或写入,它们会一直等待,直到操作完成或出现错误。

使用这些函数时,请确保正确处理返回值和错误码,以处理可能的错误情况。此外,还要注意数据的完整性和缓冲区的大小,避免数据溢出或丢失。

系统调用和库函数比较–预读入缓输出
预读入是指在从输入设备读取数据时,将一定量的数据先存储在缓冲区中,然后再逐个读取。这样可以减少对输入设备的频繁读取操作,提高读取效率。输出缓冲是指在向输出设备写入数据时,先将一定量的数据存储在缓冲区中,然后再一次性将缓冲区中的数据写入输出设备。这样可以减少对输出设备的频繁写入操作,提高写入效率。read与write函数进行cp功能时,主要慢在需要不停的从用户态切换到内核态,而使用fputc函数会维护一个缓冲(蓝色方框),只需要一次状态切换。
在这里插入图片描述

文件描述符

文件描述符(File Descriptor)是操作系统中用于标识和访问文件或其他I/O资源的整数值。在Unix-like系统(如Linux)中,每个打开的文件都会被分配一个唯一的文件描述符,它在内核中用于标识该文件。本质是一个结构体,里面存的是键值对,值是指针。图中 :

0-STDIN_FILENO
1-STDOUT_FILENO
2-STDOUT_FILENO
在这里插入图片描述

  • PCB进程控制块
    PCB(Process Control Block)是操作系统中用于管理和维护进程的数据结构。每个进程都有一个对应的PCB,用于存储与该进程相关的信息。PCB通常包含以下信息:

    1. 进程标识符(Process ID):用于唯一标识进程的值。

    2. 进程状态(Process State):表示进程当前所处的状态,如就绪、运行、阻塞等。

    3. 寄存器信息(Register Context):保存进程在被调度执行时各个寄存器的值,以便在切换上下文时恢复进程的执行状态。

    4. 进程优先级(Process Priority):用于确定进程在调度时的优先级顺序。

    5. 程序计数器(Program Counter):指示下一条要执行的指令的地址。

    6. 内存管理信息(Memory Management Information):包括进程的内存分配情况、页表信息等。

    7. 文件描述符表(File Descriptor Table):记录进程打开的文件及其对应的文件描述符。

    8. 父进程标识符(Parent Process ID):指示创建该进程的父进程的标识符。

    9. 进程资源使用情况(Resource Usage):记录进程所使用的各种资源,如CPU时间、内存占用等。

    PCB在操作系统中起着关键的作用,用于管理和调度进程的执行。当操作系统进行进程切换时,需要保存当前进程的执行状态并加载下一个进程的执行状态,这些操作都是通过操作PCB来实现的。通过PCB,操作系统可以管理进程的状态、调度进程的执行、分配和回收进程所需的资源,以及进行进程间的通信和同步等操作。PCB提供了对进程的抽象和控制,使得操作系统能够有效地管理多个并发执行的进程。

  • File结构体
    在C语言中,File结构体是用于表示文件的数据结构,它定义在stdio.h头文件中。File结构体提供了对文件的访问和操作。

    具体的File结构体定义可能会因不同的操作系统和编译器而有所不同,但通常包含以下成员:

  1. 文件描述符(File Descriptor):一个整数值,表示文件的唯一标识符或文件描述符。

  2. 文件指针(File Pointer):一个指向文件当前位置的指针,用于标识读写操作的位置。

  3. 文件状态标志(File Status Flags):用于标识文件的状态和特性,如读写模式、文件结束等。

  4. 文件错误指示器(File Error Indicator):用于标识文件操作过程中是否发生了错误。

  5. 缓冲区(Buffer):用于存储文件数据的缓冲区,可以是输入缓冲区或输出缓冲区。

    通过File结构体,程序可以进行文件的打开、读写、关闭等操作。常用的库函数如下:

    • FILE *fopen(const char *filename, const char *mode):打开文件并返回一个指向File结构体的指针。
    • int fclose(FILE *stream):关闭文件。
    • size_t fread(void *ptr, size_t size, size_t count, FILE *stream):从文件中读取数据到指定的内存缓冲区。
    • size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream):将指定的数据写入文件。
    • int fseek(FILE *stream, long offset, int whence):设置文件指针的位置。
    • int feof(FILE *stream):检查文件结束标志。
    • int ferror(FILE *stream):检查文件错误指示器。

    File结构体隐藏了底层的文件描述符等细节,提供了更高级别的文件操作接口,使得文件的读写更加方便和易于使用。

fcntl/ioctl

fcntl函数(File Control)是一个用于对文件描述符进行各种控制操作的系统调用函数。它定义在 <fcntl.h> 头文件中。

fcntl函数的原型如下:

int fcntl(int fd, int cmd, ... /* arg */);
  • fd:要进行操作的文件描述符。
  • cmd:要执行的命令,指定了要进行的具体操作。
  • arg:根据不同的命令,可能需要传递一些额外的参数。

fcntl函数的常见用途包括:

  1. 文件复制(File Duplication):可以使用F_DUPFD命令复制一个文件描述符,返回一个新的文件描述符。

  2. 文件状态标志设置和获取(File Status Flags):可以使用F_GETFL命令获取文件的状态标志,使用F_SETFL命令设置文件的状态标志。

  3. 非阻塞I/O设置和获取(Non-blocking I/O):可以使用F_GETFLF_SETFL命令来设置或获取文件的非阻塞标志。

  4. 文件加锁(File Locking):可以使用F_SETLKF_SETLKW命令来设置文件的锁定状态。

  5. 文件区域加锁(File Region Locking):可以使用F_SETLKF_SETLKW命令来对文件的特定区域进行加锁。

  6. 文件描述符的属性获取和设置(File Descriptor Flags):可以使用F_GETFDF_SETFD命令来获取和设置文件描述符的属性。

  7. 文件取消记录锁(File Advisory Locks):可以使用F_SETLKF_SETLKW命令来设置或释放文件的取消记录锁。
    在这里插入图片描述

ioctl(Input/Output Control)是一个用于对设备进行各种控制操作的系统调用函数。它提供了一种通用的接口,用于对设备驱动程序进行通信和控制。ioctl函数的原型如下:

int ioctl(int fd, unsigned long request, ... /* arg */);
  • fd:要进行操作的设备文件描述符。

  • request:指定要执行的命令,用于表示特定的操作。

  • arg:根据不同的命令,可能需要传递一些额外的参数。

    ioctl函数的具体使用方式和可用的命令取决于所操作设备的驱动程序和底层操作系统。常见的用途包括:

    1. 设置设备参数(Set Device Parameters):可以使用特定的命令和参数来设置设备的属性,如串口通信速率、网络接口配置等。

    2. 获取设备状态(Get Device Status):可以使用特定的命令和参数来获取设备的当前状态信息,如设备是否可用、设备的版本号等。

    3. 控制设备行为(Control Device Behavior):可以使用特定的命令和参数来控制设备的行为,如启动或停止设备、发送特定的控制信号等。

    4. 传输数据(Data Transfer):可以使用特定的命令和参数来进行数据的传输和交互,如向设备写入数据或从设备读取数据。

    5. 设备特定操作(Device-Specific Operations):某些设备可能提供了特定的命令和参数,用于执行设备特定的操作,如摄像头的拍照、打印机的打印控制等。

lseek函数

lseek函数是C语言中用于设置文件偏移量(文件指针位置)的函数,它定义在 <unistd.h> 头文件中。

lseek函数的原型如下:

off_t lseek(int fd, off_t offset, int whence);
  • fd:要进行操作的文件描述符。
  • offset:偏移量,表示要移动的字节数。
  • whence:指定偏移量的基准位置,可以是以下值之一:
    • SEEK_SET:从文件开头开始计算偏移量。
    • SEEK_CUR:从当前文件位置计算偏移量。
    • SEEK_END:从文件末尾计算偏移量。

lseek函数用于设置文件的读写位置,可以在文件中进行随机访问。具体的操作包括:

  • 如果 whence 参数为 SEEK_SET,则将文件指针设置为距离文件开头 offset 字节的位置。
  • 如果 whence 参数为 SEEK_CUR,则将文件指针设置为距离当前位置 offset 字节的位置。
  • 如果 whence 参数为 SEEK_END,则将文件指针设置为距离文件末尾 offset 字节的位置。

lseek函数返回新的文件偏移量(相对于文件开头),如果发生错误,则返回-1,并设置全局变量 errno 来指示具体的错误原因。

注意:lseek函数一般用于对普通文件进行偏移操作,对于某些特殊类型的文件(如管道、套接字等),使用lseek可能会产生不确定的行为或失败。在使用lseek函数时,应注意检查返回值和错误处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值