C语言——文件描述符、系统调用操作文件

news/2024/10/21 5:59:26/

文件描述符

在Unix-like操作系统中,文件描述符(file descriptor)是一个用于标识打开文件或I/O设备的整数值。它是对底层文件系统的抽象,用于在应用程序和操作系统之间传递文件信息。

文件描述符是一个非负整数,通常是一个小整数。在C语言中,文件描述符被表示为int类型。

每个进程在其打开的文件或设备上都有一组文件描述符,它们是连续的、非重复的整数值。当一个文件或设备被打开时,操作系统会为该文件分配一个文件描述符,并将其返回给应用程序。

常见的文件描述符包括:

  • 标准输入(stdin):文件描述符为0,宏为STDIN_FILENO,通常用于接收应用程序的输入。
  • 标准输出(stdout):文件描述符为1,宏为STDOUT_FILENO,通常用于输出应用程序的结果。
  • 标准错误(stderr):文件描述符为2,宏为STDERR_FILENO,通常用于输出应用程序的错误信息。
应用程序可以使用文件描述符进行各种文件和I/O操作,例如读取文件内容、写入数据、关闭文件等。文件描述符作为操作系统和应用程序之间的桥梁,允许应用程序与文件系统和其他设备进行交互。

在Linux的世界里,一切设备皆文件。我们可以调用系统中 的 I/O函数,对文件进行相应的操作( open()、close()、write() 、read() 等)。

打开现有文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号。文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件。

程序运行起来后(每个进程)都有一张文件描述符的表,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符 0、1、2 记录在表中。程序运行起来后这三个文件描述符是默认打开的。

#define STDIN_FILENO  0 //标准输入的文件描述符
#define STDOUT_FILENO 1 //标准输出的文件描述符
#define STDERR_FILENO 2 //标准错误的文件描述符

在程序运行起来后打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中。
在这里插入图片描述
最大打开的文件个数
Linux中一个进程最多只能打开NR_OPEN_DEFAULT(即1024)个文件,故当文件不再使用时应及时调用 close() 函数关闭文件。
• 查看当前系统允许打开最大文件个数:

cat /proc/sys/fs/file-max

• 当前默认设置最大打开文件个数1024

ulimit -a

• 修改默认设置最大打开文件个数为4096

ulimit -n 4096

16. 常用文件IO函数(以下均为系统调用)

Linux的man手册共有以下几个章节:

  1. Standard commands (标准命令)
  2. System calls (系统调用)
  3. Library functions (库函数)
  4. Special devices (设备说明)
  5. File formats (文件格式)
  6. Games and toys (游戏和娱乐)
  7. Miscellaneous (杂项)
  8. Administrative Commands (管理员命令)
    使用系统调用函数依然是需要引入头文件的,要引入哪些可以通过man命令查看
man 2 命令

16.1 open函数

man 2 open
在这里插入图片描述

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:打开文件,如果文件不存在则可以选择创建。
参数:pathname:文件的路径及文件名,可以是相对路径或绝对路径flags:打开文件的行为标志,用于指定文件的打开方式和操作选项。必选项O_RDONLY\O_WRONLY\O_RDWRmode:权限参数,用于指定新创建文件的访问权限,只有在使用O_CREAT标志创建文件时才有效。
返回值:成功:成功返回打开的文件描述符失败:-1

open()函数返回一个非负整数的文件描述符(file descriptor),用于后续对文件的读写操作。如果打开文件失败,函数返回-1,并可以使用errno全局变量获取具体的错误码。
flags详细说明
必选项:

取值含义
O_RDONLY以只读的方式打开
O_WRONLY以只写的方式打开
O_RDWR以可读、可写的方式打开

可选项,和必选项按位或起来

取值含义
O_CREAT文件不存在则创建文件,使用此选项时需使用mode说明文件的权限
O_EXCL与O_CREAT一起使用,用于确保创建文件时文件不存在。如果同时指定了O_CREAT,且文件已经存在,则出错
O_TRUNC如果文件存在,则清空文件内容
O_APPEND写文件时,数据添加到文件末尾
O_NONBLOCK对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O

mode补充说明

  1. 文件最终权限:mode & ~umask
  2. shell进程的umask掩码可以用umask命令查看

Ø umask:查看掩码(补码)
Ø umask mode:设置掩码,mode为八进制数
Ø umask -S:查看各组用户的默认操作权限

取值八进制含义
S_IRWXU00700文件所有者的读、写、可执行权限
S_IRUSR00400文件所有者的读权限
S_IWUSR00200文件所有者的写权限
S_IXUSR00100文件所有者的可执行权限
S_IRWXG00070文件所有者同组用户的读、写、可执行权限
S_IRGRP00040文件所有者同组用户的读权限
S_IWGRP00020文件所有者同组用户的写权限
S_IXGRP00010文件所有者同组用户的可执行权限
S_IRWXO00007其他组用户的读、写、可执行权限
S_IROTH00004其他组用户的读权限
S_IWOTH00002其他组用户的写权限
S_IXOTH00001其他组用户的可执行权限

16.2 close函数

在这里插入图片描述

#include <unistd.h>
int close(int fd);
功能关闭已打开的文件
参数:fd: 文件描述符,open()的返回值
返回值:成功:0失败:-1, 并设置errno

需要说明的是,当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。

但是对于一个长年累月运行的程序(比如网络服务器),打开的文件描述符一定要记得关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。

16.3 write函数

在这里插入图片描述

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:把指定数目的数据写到文件(fd),将buf指向的数据写入到文件描述符fd所指向的文件或设备中。
参数:fd: 文件描述符,可以是标准输出(STDOUT_FILENO,值为1)、标准错误(STDERR_FILENO,值为2),或者是通过open函数打开的文件描述符。buf: 数据首地址,要写入数据的缓冲区的指针。count: 写入数据的长度(字节)
返回值:成功:实际写入数据的字节个数失败: - 1

write() 函数返回一个 ssize_t 类型的值,表示成功写入的字节数。返回值有以下几种情况:

  • 如果返回值大于等于 0,则表示成功写入了指定字节数。
  • 如果返回值为 -1,则表示发生了错误。此时,可以通过 errno 全局变量来获取具体的错误代码。
  • 如果返回值为 0,则表示没有写入任何数据。这通常发生在写入到非阻塞文件描述符时,当写入缓冲区已满或遇到信号中断时。
需要注意的是,write() 函数可能不会一次性写入所有请求的字节数。在某些情况下,它可能会写入部分数据并返回写入的字节数。如果需要确保全部数据写入,可能需要多次调用 write() 直到写入的字节数达到预期。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>//write file
int main(void) {int fd=-1;int ret=-1;char *str="hello test";
//打开或者创建一个文件,返回文件描述符。文件名就叫做txtfd=open("txt",O_WRONLY | O_CREAT,0644);if(-1==fd){perror("open");return 1;}printf("fd=%d\n",fd);
//往文件中写入内容ret=write(fd,str,strlen(str));if (-1==ret){perror("write");return 1;}
//长度应该是hello test共10个char占10个字节printf("write len:%d\n",ret);close(fd);return 0;
}

16.4 read函数

在这里插入图片描述

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:把指定数目的数据读到内存(缓冲区)。从文件描述符fd所指向的文件或设备中读取数据,并将其存储到buf所指向的缓冲区中。
参数:fd: 文件描述符,它可以是标准输入(STDIN_FILENO,值为0)、标准输出(STDOUT_FILENO,值为1)、标准错误(STDERR_FILENO,值为2),或者是通过open函数打开的文件描述符。buf: 内存首地址,用于存储读取数据的缓冲区的指针count: 读取的字节个数
返回值:成功:实际读取到的字节个数失败: - 1

read函数返回实际读取的字节数,如果返回值为0,则表示已到达文件末尾,如果返回值为-1,则表示出现错误。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>//read file
#define SIZE 128
int main(void) {int fd=-1;int ret=-1;char *str="hello test";//打开或者创建一个文件,返回文件描述符。文件名就叫做txtfd=open("txt",O_RDONLY);if(-1==fd){perror("open");return 1;}printf("fd=%d\n",fd);//读取文件中内容char buf[SIZE];memset(buf,0,SIZE);ret=read(fd,buf,SIZE);if (-1==ret){perror("read");return 1;}//长度应该是hello test共10个char占10个字节printf("read len:%d\n",ret);close(fd);return 0;
}

阻塞和非阻塞的概念
读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。

从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。

同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。

【注意】阻塞与非阻塞是对于文件而言的,而不是指read、write等的属性。

以非阻塞方式打开文件程序示例:

#include <unistd.h> //read
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h> //EAGAINint main() {// /dev/tty --> 当前终端设备// 以不阻塞方式(O_NONBLOCK)打开终端设备,0 1 2进程启动就被占用了,所以最小fd为3int fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);char buf[10];int n;n = read(fd, buf, sizeof(buf));if (n < 0) {// 如果为非阻塞,但是没有数据可读,此时全局变量 errno 被设置为 EAGAINif (errno != EAGAIN) {perror("read /dev/tty");return -1;}printf("没有数据\n");}return 0;
}
#include <stdio.h>
int main(void) {//阻塞char ch=-1;ch=getchar();putchar(ch);
}

16.5 lseek函数

在这里插入图片描述

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
功能:改变文件的偏移量
参数:fd:文件描述符offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。whence:其取值如下:SEEK_SET:从文件开头移动offset个字节SEEK_CUR:从当前位置移动offset个字节SEEK_END:从文件末尾移动offset个字节
返回值:若lseek成功执行, 则返回新的偏移量如果失败, 返回-1

所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。

读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了 O_APPEND 。


http://www.ppmy.cn/news/1457855.html

相关文章

【FX110】2024外汇市场中交易量最大的货币对是哪个?

作为最大、最流动的金融市场之一&#xff0c;外汇市场每天的交易量高达几万亿美元&#xff0c;涉及到数百种货币。不同货币对的交易活跃程度并不一样&#xff0c;交易者需要根据货币对各自的特点去进行交易。 全年外汇市场中涉及美元的外汇交易超过50%&#xff01; 实际上&…

优化电脑空间清理电脑占用磁盘空间垃圾

1. 清理磁盘 右下角放大镜&#xff0c;搜索 此电脑 点击要清理的磁盘 &#xff0c;比如点击C盘&#xff0c;右键属性&#xff0c;常规选项卡&#xff0c;点击清理磁盘&#xff0c; 和点击清理系统文件 1.1 优化磁盘 右下角放大镜&#xff0c;搜索 此电脑 点击要清理的磁盘 &…

VS2022Qt6通过ODBC连接MySQL

QSqlDatabase是Qt框架中用于管理数据库连接的类。它提供了一种在Qt应用程序中连接和操作数据库的方式。通过QSqlDatabase&#xff0c;可以连接到各种类型的数据库&#xff0c;并执行查询、插入、更新和删除等操作&#xff0c;Qt通过ODBC连接数据库的第一步就是初始化QSqlDataba…

LabelImg下载及目标检测数据标注

为什么这一部分内容这么少会单独拎出来呢&#xff0c;因为后期会接着介绍YOLOv8中的其他任务&#xff0c;会使用其他软件进行标注&#xff0c;所以就单独区分开来每一个任务的标注方式了。 这一部分就介绍目标检测任务的标注&#xff0c;数据集是我从COCO2017Val中抽出来两类&a…

vue3 element-plus表单form验证规则设置的require:true无效

必填项为空校验&#xff0c;valid第一次为true&#xff0c;再点值为false 引入FormInstance &#xff0c;校验代码改为以下&#xff1a; import { ElMessage, FormInstance } from element-plus const ruleFormRef ref<FormInstance>()const submitForm async (formEl:…

如何快速优雅的免费申请和搭建属于自己的服务器

今天来讲一下如何快速优雅的搭建属于自己的服务器&#xff0c;我们以阿里云的云服务器为例&#xff0c;新用户一般是有三个月使用期限。 首先我们进入官网&#xff0c;选择云服务器ecs 链接直达&#xff1a;https://cn.aliyun.com 打开网页后&#xff0c;往下滑&#xff0c;然…

java集合中retainAll方法使用注意

java集合中retainAll方法使用注意 retainAll新的改变 retainAll retainAll()方法被用来找出两个集合的共同元素&#xff0c;并且只会在交集不为空的情况下返回true。通过比较操作前后集合的大小是否有变化&#xff0c;我们可以确定是否存在共同元素。 retainAl()方法会改变原集…

17、Flink 的 Checkpointing 配置详解

Checkpointing 1.概述 Flink 中的每个方法或算子都能够是有状态的&#xff0c;状态化的方法在处理单个 元素/事件 的时候存储数据&#xff0c;为了让状态容错&#xff0c;Flink 需要为状态添加 checkpoint&#xff08;检查点&#xff09;。 2.开启与配置 Checkpoint 默认 c…