一、文件I/O简介Linux文件I/O是操作系统中处理文件读写操作的基本机制。在Linux系统中文件I/O操作是通过系统调用实现的这些系统调用允许用户空间的程序与内核空间的文件系统进行交互。一个通用的IO模型通常包括打开文件、读写文件、关闭文件这些基本操作。文件描述符File Descriptor在 Linux 系统中每个打开的文件都由一个非负整数的文件描述符来标识。当程序打开一个现有文件或者创建一个新文件时内核会返回一个文件描述符。标准输入stdin、标准输出stdout和标准错误输出stderr的文件描述符分别是 0、1 和 2。缓冲区Buffer为了提高文件 I/O 的效率系统通常会使用缓冲区。缓冲区是一块内存区域用于临时存储从文件读取或要写入文件的数据。二、文件描述符文件描述符File Descriptor是Linux和UNIX系统编程中的一个重要概念它是一个用于标识打开文件或其他输入/输出资源的非负整数。文件描述符允许程序通过一个抽象的数字来引用文件和其他输入输出资源而不是直接使用文件名或设备名。2.1. 唯一性在进程的生命周期内每个打开的文件或设备都会分配一个唯一的文件描述符。这些描述符是从3开始分配的因为0、1、2已经被系统预留给标准输入stdin、标准输出stdout和标准错误stderr了。例如如果进程首先打开一个文件它将被分配文件描述符3接着打开第二个文件则分配文件描述符4以此类推。代码语言javascriptAI代码解释#include stdio.h #include fcntl.h #include unistd.h int main() { int fd1 open(file1.txt, O_RDONLY); int fd2 open(file2.txt, O_RDONLY); int fd3 open(file3.txt, O_RDONLY); if (fd1 -1 || fd2 -1 || fd3 -1) { perror(open); return 1; } printf(File descriptors: file1.txt %d, file2.txt %d, file3.txt %d\n, fd1, fd2, fd3); close(fd1); close(fd2); close(fd3); return 0; }我们打开了三个文件并打印了它们的文件描述符。通过运行可以观察到文件描述符是从3开始递增分配的假设0、1、2没有被占用或重定向。2.2. 抽象性文件描述符提供了一种抽象机制使得程序可以通过简单的数字来引用复杂的I/O资源。这种抽象性简化了编程模型因为程序员不需要关心底层的设备或文件实现细节。文件描述符的这种抽象性也支持了重定向和管道等高级I/O操作。例如可以将一个进程的标准输出重定向到一个文件或者将一个进程的输出作为另一个进程的输入这些操作都可以通过操作文件描述符来实现。代码语言javascriptAI代码解释#include stdio.h #include fcntl.h #include unistd.h #include string.h int main() { int fd open(example.txt, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd -1) { perror(open); return 1; } const char *text Hello, file descriptor!\n; ssize_t bytes_written write(fd, text, strlen(text)); if (bytes_written -1) { perror(write); close(fd); return 1; } close(fd); return 0; }打开或创建了一个文件并使用write函数通过文件描述符向其中写入数据。文件描述符在这里作为I/O操作的抽象引用。2.3. 有限性文件描述符的数量是有限的这个限制通常由系统设置决定。在Linux系统中可以使用ulimit -n命令来查看和设置当前shell进程的文件描述符限制。默认情况下这个限制可能比较低如1024但在现代系统中这个限制通常可以被提高。提高文件描述符限制对于需要打开大量文件的服务器程序来说是非常重要的。需要注意的是虽然系统允许提高文件描述符限制但这也受到系统资源如内存的限制。打开过多的文件可能会导致系统资源耗尽从而影响系统的稳定性和性能。代码语言javascriptAI代码解释#include stdio.h #include stdlib.h #include unistd.h #include sys/resource.h int main() { struct rlimit rl; // 获取当前文件描述符限制 if (getrlimit(RLIMIT_NOFILE, rl) -1) { perror(getrlimit); exit(EXIT_FAILURE); } printf(Current file descriptor limit: soft %lld, hard %lld\n, (long long)rl.rlim_cur, (long long)rl.rlim_max); // 尝试提高软限制在硬限制范围内 rl.rlim_cur rl.rlim_max; // 或者设置为一个较小的值但不超过硬限制 if (setrlimit(RLIMIT_NOFILE, rl) -1) { perror(setrlimit); exit(EXIT_FAILURE); } // 再次获取限制以确认更改 if (getrlimit(RLIMIT_NOFILE, rl) -1) { perror(getrlimit); exit(EXIT_FAILURE); } printf(New file descriptor limit: soft %lld, hard %lld\n, (long long)rl.rlim_cur, (long long)rl.rlim_max); return 0; }首先获取了当前的文件描述符限制软限制和硬限制然后尝试将软限制提高到硬限制的值。请注意硬限制是由系统管理员设置的普通用户可能无法更改它。如果尝试设置一个超过硬限制的值setrlimit调用将失败。三、文件操作函数在Linux系统中文件操作主要涉及到以下几个函数open()函数用于打开文件。其原型为int open(const char *pathname, int flags, mode_t mode)。其中pathname是文件名或路径flags用于指定文件的打开模式如只读、只写、读写等mode用于设置文件权限当创建新文件时。read()函数用于从文件中读取数据。其原型为ssize_t read(int fd, void *buf, size_t count)。其中fd是文件描述符buf是指向存储读取数据的缓冲区的指针count是要读取的字节数。write()函数用于向文件中写入数据。其原型为ssize_t write(int fd, const void *buf, size_t count)。参数含义与read()函数类似。close()函数用于关闭文件。其原型为int close(int fd)。其中fd是文件描述符。lseek()函数用于移动文件指针。其原型为off_t lseek(int fd, off_t offset, int whence)。其中fd是文件描述符offset是偏移量whence用于指定偏移的基准位置如文件开头、当前位置、文件末尾等。creat()函数用于创建文件。其原型为int creat(const char *pathname, mode_t mode)。其中pathname是文件名或路径mode用于设置文件权限。不过在现代Linux系统中creat()函数已经被open()函数所取代因为open()函数提供了更丰富的功能。四、标准文件I/O函数除了上述低级的文件操作函数外Linux还提供了一套标准的文件I/O函数这些函数封装了复杂的底层细节便于用户进行日常文件操作。标准文件I/O函数主要包括fopen()函数用于打开文件。其原型为FILE *fopen(const char *filename, const char *mode)。fclose()函数用于关闭文件。其原型为int fclose(FILE *stream)。fread()函数用于从文件中读取数据。其原型为size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)。fwrite()函数用于向文件中写入数据。其原型为size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)。fgets()函数用于从文件中读取一行字符。其原型为char *fgets(char *str, int n, FILE *stream)。fputs()函数用于向文件中写入字符串。其原型为int fputs(const char *str, FILE *stream)。五、文件执行权限在Linux系统中文件执行权限是控制用户可以对文件执行哪些操作的重要机制。正确设置文件权限对于系统的安全性至关重要5.1. 权限类型Linux文件权限主要分为三类读权限r允许用户读取文件内容或列出目录内容。写权限w允许用户修改文件内容或在目录中创建、删除文件。执行权限x允许用户执行文件如果文件是可执行文件或进入目录。5.2. 权限分配对象这些权限可以被分配给以下三个对象文件所有者owner文件的创建者或拥有者对文件具有最高的控制权限。文件所属组group文件所属的用户组组内的所有用户共享这些权限。其他用户others既不是文件所有者也不属于文件所属组的用户。5.3. 权限表示方法Linux系统提供两种表示文件权限的方法数字表示法和符号表示法。数字表示法读权限r 4写权限w 2执行权限x 1将这三种权限的数字相加就可以得到每个用户类别的权限值。例如7表示读、写和执行权限4215表示读和执行权限41。符号表示法使用字符来表示权限通常与用户名、组名一起显示在ls -l命令的输出中。例如-rwxr-xr--表示一个普通文件所有者有读、写和执行权限组用户有读和执行权限其他用户只有读权限。5.4. 权限设置命令在Linux中可以使用chmod命令来设置或修改文件权限。符号表示法设置权限chmod ux 文件名给文件的所有者添加执行权限。chmod gw,or 文件名给用户组增加写权限给其他用户增加读权限。chmod ar 文件名将文件的权限设置为所有人仅具有读权限。数字表示法设置权限chmod 755 文件名设置文件所有者为读写执行权限7用户组和其他用户为读执行权限5。chmod 644 文件名设置文件所有者为读写权限6用户组和其他用户为读权限4。5.5. 权限设置的重要性正确设置文件权限对于Linux系统的安全性至关重要。通过合理设置文件权限可以控制不同用户对文件和目录的访问和操作防止未经授权的访问和修改从而保护系统资源的安全。5.6. 实例说明假设有一个名为script.sh的Shell脚本文件需要给其所有者添加执行权限以便能够执行该脚本。可以使用以下命令代码语言javascriptAI代码解释chmod ux script.sh或者也可以使用数字表示法来设置权限代码语言javascriptAI代码解释chmod 755 script.sh这样script.sh文件的所有者将拥有读、写和执行权限而用户组和其他用户将拥有读和执行权限虽然对于脚本文件来说写权限通常不是必需的但这里为了演示目的而包含。六、设备文件读写在嵌入式Linux系统中设备文件是一种将硬件设备抽象为普通文件的机制。这种抽象使得用户空间程序可以通过标准的文件I/O操作如open、read、write、close等来与硬件设备进行交互。设备文件通常位于/dev目录下并且根据其特性被分类为字符设备或块设备。6.1. 设备文件类型字符设备字符设备以字符为单位进行数据传输如串口UART、键盘、鼠标等。对字符设备的读写操作通常不会涉及缓存因为数据是即时处理的。块设备块设备以块通常是512字节或更大为单位进行数据传输如硬盘、SD卡等。对块设备的读写操作可能会涉及缓存以提高性能。6.2. 设备文件的命名设备文件通常以设备类型加上设备编号的形式命名。例如/dev/ttyS0可能表示第一个串口设备而/dev/sda1可能表示第一个SCSI硬盘的第一个分区。6.3. 设备文件的读写操作在嵌入式编程中对设备文件的读写操作通常涉及以下步骤。打开设备文件使用open函数打开设备文件指定操作模式如读、写或读写。配置设备如果需要对于某些设备可能需要通过ioctl函数发送控制命令来配置设备参数。读写操作使用read函数从设备读取数据。使用write函数向设备写入数据。处理错误检查每个系统调用的返回值以处理可能的错误情况。关闭设备文件使用close函数关闭设备文件释放资源。