本文转载自链接: https://blog.csdn.net/weixin_37800531/article/details/145558926
在嵌入式 Linux 应用开发中,文件 I/O(Input/Output)基础编程是非常重要的一部分,它允许程序与文件系统进行交互,实现数据的读取、写入和管理等操作。
Linux文件I/O是操作系统中处理文件读写操作的基本机制。在Linux系统中,文件I/O操作是通过系统调用实现的,这些系统调用允许用户空间的程序与内核空间的文件系统进行交互。一个通用的IO模型通常包括打开文件、读写文件、关闭文件这些基本操作。
文件描述符(File Descriptor)是Linux和UNIX系统编程中的一个重要概念,它是一个用于标识打开文件或其他输入/输出资源的非负整数。文件描述符允许程序通过一个抽象的数字来引用文件和其他输入输出资源,而不是直接使用文件名或设备名。
在进程的生命周期内,每个打开的文件或设备都会分配一个唯一的文件描述符。这些描述符是从3开始分配的,因为0、1、2已经被系统预留给标准输入(stdin)、标准输出(stdout)和标准错误(stderr)了。
例如,如果进程首先打开一个文件,它将被分配文件描述符3;接着打开第二个文件,则分配文件描述符4,以此类推。
#include
#include
#include
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没有被占用或重定向)。
文件描述符提供了一种抽象机制,使得程序可以通过简单的数字来引用复杂的I/O资源。这种抽象性简化了编程模型,因为程序员不需要关心底层的设备或文件实现细节。
文件描述符的这种抽象性也支持了重定向和管道等高级I/O操作。例如,可以将一个进程的标准输出重定向到一个文件,或者将一个进程的输出作为另一个进程的输入,这些操作都可以通过操作文件描述符来实现。
#include
#include
#include
#include
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操作的抽象引用。
文件描述符的数量是有限的,这个限制通常由系统设置决定。在Linux系统中,可以使用ulimit -n
命令来查看和设置当前shell进程的文件描述符限制。
默认情况下,这个限制可能比较低(如1024),但在现代系统中,这个限制通常可以被提高。提高文件描述符限制对于需要打开大量文件的服务器程序来说是非常重要的。
需要注意的是,虽然系统允许提高文件描述符限制,但这也受到系统资源(如内存)的限制。打开过多的文件可能会导致系统资源耗尽,从而影响系统的稳定性和性能。
#include
#include
#include
#include
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系统中,文件操作主要涉及到以下几个函数:
int open(const char *pathname, int flags, mode_t mode)
。其中,pathname
是文件名或路径,flags
用于指定文件的打开模式(如只读、只写、读写等),mode
用于设置文件权限(当创建新文件时)。ssize_t read(int fd, void *buf, size_t count)
。其中,fd
是文件描述符,buf
是指向存储读取数据的缓冲区的指针,count
是要读取的字节数。ssize_t write(int fd, const void *buf, size_t count)
。参数含义与read()
函数类似。int close(int fd)
。其中,fd
是文件描述符。off_t lseek(int fd, off_t offset, int whence)
。其中,fd
是文件描述符,offset
是偏移量,whence
用于指定偏移的基准位置(如文件开头、当前位置、文件末尾等)。int creat(const char *pathname, mode_t mode)
。其中,pathname
是文件名或路径,mode
用于设置文件权限。不过,在现代Linux系统中,creat()
函数已经被open()
函数所取代,因为open()
函数提供了更丰富的功能。除了上述低级的文件操作函数外,Linux还提供了一套标准的文件I/O函数,这些函数封装了复杂的底层细节,便于用户进行日常文件操作。标准文件I/O函数主要包括:
FILE *fopen(const char *filename, const char *mode)
。int fclose(FILE *stream)
。size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
。size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
。char *fgets(char *str, int n, FILE *stream)
。int fputs(const char *str, FILE *stream)
。在Linux系统中,文件执行权限是控制用户可以对文件执行哪些操作的重要机制。正确设置文件权限对于系统的安全性至关重要
Linux文件权限主要分为三类:
这些权限可以被分配给以下三个对象:
Linux系统提供两种表示文件权限的方法:数字表示法和符号表示法。
数字表示法:
符号表示法:使用字符来表示权限,通常与用户名、组名一起显示在ls -l
命令的输出中。例如,-rwxr-xr--
表示一个普通文件,所有者有读、写和执行权限,组用户有读和执行权限,其他用户只有读权限。
在Linux中,可以使用chmod
命令来设置或修改文件权限。
符号表示法设置权限:
chmod u+x 文件名
:给文件的所有者添加执行权限。chmod g+w,o+r 文件名
:给用户组增加写权限,给其他用户增加读权限。chmod a=r 文件名
:将文件的权限设置为所有人仅具有读权限。数字表示法设置权限:
chmod 755 文件名
:设置文件所有者为读写执行权限(7),用户组和其他用户为读执行权限(5)。chmod 644 文件名
:设置文件所有者为读写权限(6),用户组和其他用户为读权限(4)。正确设置文件权限对于Linux系统的安全性至关重要。通过合理设置文件权限,可以控制不同用户对文件和目录的访问和操作,防止未经授权的访问和修改,从而保护系统资源的安全。
假设有一个名为script.sh
的Shell脚本文件,需要给其所有者添加执行权限,以便能够执行该脚本。可以使用以下命令:
chmod u+x script.sh
或者,也可以使用数字表示法来设置权限:
chmod 755 script.sh
这样,script.sh
文件的所有者将拥有读、写和执行权限,而用户组和其他用户将拥有读和执行权限(虽然对于脚本文件来说,写权限通常不是必需的,但这里为了演示目的而包含)。
在嵌入式Linux系统中,设备文件是一种将硬件设备抽象为普通文件的机制。这种抽象使得用户空间程序可以通过标准的文件I/O操作(如open
、read
、write
、close
等)来与硬件设备进行交互。设备文件通常位于/dev
目录下,并且根据其特性被分类为字符设备或块设备。
/dev/ttyS0
可能表示第一个串口设备,而/dev/sda1
可能表示第一个SCSI硬盘的第一个分区。在嵌入式编程中,对设备文件的读写操作通常涉及以下步骤。
open
函数打开设备文件,指定操作模式(如读、写或读写)。ioctl
函数发送控制命令来配置设备参数。read
函数从设备读取数据。write
函数向设备写入数据。close
函数关闭设备文件,释放资源。以下是一个简单的示例,展示如何通过读写串口设备文件来进行通信:
#include
#include
#include
#include
#include
#include
int main() {
int fd;
struct termios options;
// 打开串口设备文件
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
// 配置串口参数
tcgetattr(fd, &options);
cfsetispeed(&options, B9600); // 设置输入波特率
cfsetospeed(&options, B9600); // 设置输出波特率
options.c_cflag |= (CLOCAL | CREAD); // 启用接收器,忽略调制解调器控制线
options.c_cflag &= ~PARENB; // 无奇偶校验
options.c_cflag &= ~CSTOPB; // 一个停止位
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8; // 8个数据位
tcsetattr(fd, TCSANOW, &options);
// 写入数据到串口
const char *msg = "Hello, UART!\n";
write(fd, msg, strlen(msg));
// 从串口读取数据(这里只是示例,实际应用中可能需要循环读取)
char buf[256];
int n = read(fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0'; // 确保字符串以null结尾
printf("Received: %s", buf);
} else if (n < 0) {
perror("read");
}
// 关闭串口设备文件
close(fd);
return 0;
}
首先打开了/dev/ttyS0
设备文件,配置了串口参数(如波特率、数据位、停止位等),然后向串口写入了数据,并从串口读取了数据(虽然在实际应用中,读取操作通常是在一个循环中进行的)。最后,关闭了设备文件。
综上所述,嵌入式Linux应用开发中的文件I/O基础编程涉及到文件描述符、文件操作函数、标准文件I/O函数以及文件执行权限等多个方面。掌握这些基础知识对于进行嵌入式Linux应用开发至关重要。