1.1 函数原型

代码语言:javascript

AI代码解释

#include <fcntl.h>
int creat(const char *pathname, mode_t mode);
1.2 参数说明​

  • pathname:指向要创建的文件路径名的字符串指针。可以是绝对路径,也可以是相对路径。​
  • mode:指定新文件的权限模式。它由一系列权限位组成,这些权限位决定了文件所有者、所属组和其他用户对该文件的访问权限。常见的权限位有:​
    • S_IRUSR:所有者具有读权限(0400)​
    • S_IWUSR:所有者具有写权限(0200)​
    • S_IXUSR:所有者具有执行权限(0100)​
    • S_IRGRP:所属组具有读权限(0040)​
    • S_IWGRP:所属组具有写权限(0020)​
    • S_IXGRP:所属组具有执行权限(0010)​
    • S_IROTH:其他用户具有读权限(0004)​
    • S_IWOTH:其他用户具有写权限(0002)​
    • S_IXOTH:其他用户具有执行权限(0001)​

这些权限位可以通过按位或(|)运算组合使用,例如,S_IRUSR | S_IWUSR 表示所有者具有读写权限。​

1.3 返回值​
  • 成功:返回一个非负的文件描述符(file descriptor),该文件描述符用于后续对文件的操作。​
  • 失败:返回 - 1,并设置 errno 来指示错误类型。常见的错误有:​

  • EEXIST:指定的文件已经存在,且没有被截断(当使用 O_CREAT | O_EXCL 标志时,如果文件存在则会返回该错误,但 creat 系统调用本身如果文件存在会截断文件,所以此错误在 creat 中较少见)​
  • ENOENT:路径名中的目录不存在​
  • EACCES:没有权限创建文件或访问路径中的目录​
  • ENOSPC:文件系统没有足够的空间创建新文件​
1.4 使用示例

代码语言:javascript

AI代码解释

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

int main() {
    int fd;
    // 创建一个名为"testfile.txt"的文件,所有者具有读写权限,所属组和其他用户无权限
    fd = creat("testfile.txt", S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("creat failed");
        exit(EXIT_FAILURE);
    }
    printf("File created successfully with file descriptor: %d\n", fd);
    close(fd); // 创建文件后要记得关闭文件描述符
    return 0;
}

使用 creat 函数创建了一个名为 “testfile.txt” 的文件,设置所有者具有读写权限。如果创建成功,会输出文件描述符;如果失败,会通过 perror 函数打印错误信息。​

二、文件打开(open 系统调用)​

open 系统调用是 Linux 中用于打开一个已存在的文件或创建一个新文件的主要系统调用。它比 creat 系统调用功能更强大,可以通过不同的标志组合实现多种操作。​

2.1 函数原型

代码语言:javascript

AI代码解释

#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
2.2 参数说明​

pathname:与 creat 系统调用中的 pathname 参数相同,指向要打开或创建的文件路径名。​

flags:用于指定打开文件的方式和行为,是一个整数,可以由多个标志按位或组合而成。常见的标志有:​

  • O_RDONLY:以只读方式打开文件​
  • O_WRONLY:以只写方式打开文件​
  • O_RDWR:以读写方式打开文件​
  • O_CREAT:如果指定的文件不存在,则创建该文件。此时需要提供第三个参数 mode 来指定新文件的权限。​
  • O_EXCL:与 O_CREAT 一起使用,如果文件已经存在,则 open 调用失败(返回 - 1),可以用于防止覆盖已存在的文件。​
  • O_TRUNC:如果文件已经存在且以可写方式打开(O_WRONLY 或 O_RDWR),则将文件截断为零长度。​
  • O_APPEND:以追加方式打开文件,每次写操作都将数据添加到文件的末尾。​

mode:当 flags 中包含 O_CREAT 标志时,该参数用于指定新文件的权限模式,与 creat 系统调用中的 mode 参数相同。如果 flags 中不包含 O_CREAT,则该参数被忽略。​

2.3 返回值​
  • 成功:返回一个非负的文件描述符,用于后续对文件的操作。​
  • 失败:返回 - 1,并设置 errno 指示错误类型。常见的错误与 creat 系统调用类似,此外还有:​
    • EINVAL:flags 参数无效​
    • EMFILE:进程已打开的文件描述符达到上限​
2.4 使用示例

代码语言:javascript

AI代码解释

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    int fd1, fd2, fd3;

    // 以只读方式打开已存在的文件"testfile.txt"
    fd1 = open("testfile.txt", O_RDONLY);
    if (fd1 == -1) {
        perror("open for read failed");
        exit(EXIT_FAILURE);
    }

    // 以读写方式打开文件,如果文件不存在则创建,权限为所有者读写,组和其他用户只读
    fd2 = open("newfile.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if (fd2 == -1) {
        perror("open for read-write and create failed");
        close(fd1);
        exit(EXIT_FAILURE);
    }

    // 以只写、追加方式打开文件,若文件不存在则创建
    fd3 = open("logfile.txt", O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
    if (fd3 == -1) {
        perror("open for write and append failed");
        close(fd1);
        close(fd2);
        exit(EXIT_FAILURE);
    }

    printf("fd1: %d, fd2: %d, fd3: %d\n", fd1, fd2, fd3);

    // 关闭文件描述符
    close(fd1);
    close(fd2);
    close(fd3);

    return 0;
}

展示了 open 系统调用的几种常见用法:只读打开已存在文件、读写打开并创建新文件、只写追加打开并创建新文件。​

三、文件读写(read 和 write 系统调用)​

打开文件后,就可以进行读写操作了。Linux 提供了 read 和 write 系统调用来实现对文件的读写。​

3.1 read 系统调用​

read 系统调用用于从已打开的文件中读取数据。​

①函数原型

代码语言:javascript

AI代码解释

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

②参数说明​

  • fd:文件描述符,即 open 或 creat 系统调用返回的非负整数,标识要读取的文件。​
  • buf:指向一个缓冲区的指针,用于存储读取到的数据。​
  • count:指定要读取的字节数。​

③ 返回值​

  • 成功:返回实际读取到的字节数。如果已经到达文件末尾,则返回 0。​
  • 失败:返回 - 1,并设置 errno 指示错误类型,如 EBADF(文件描述符无效)、EIO(I/O 错误)等。​
3.2 write 系统调用​

write 系统调用用于向已打开的文件中写入数据。​

①函数原型

代码语言:javascript

AI代码解释

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

②参数说明​

  • fd:文件描述符,标识要写入的文件。​
  • buf:指向一个缓冲区的指针,该缓冲区中存储了要写入的数据。​
  • count:指定要写入的字节数。​

③返回值​

  • 成功:返回实际写入的字节数。注意,实际写入的字节数可能小于 count,这并不一定表示错误,可能是由于各种原因(如磁盘空间不足、信号中断等)导致的。​
  • 失败:返回 - 1,并设置 errno 指示错误类型,如 EBADF(文件描述符无效或不具有写权限)、EIO(I/O 错误)等。​
3.3 使用示例

代码语言:javascript

AI代码解释

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd;
    char write_buf[] = "Hello, Linux File Operation!";
    char read_buf[1024];
    ssize_t nwritten, nread;

    // 以读写方式打开文件,不存在则创建
    fd = open("rwtest.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open failed");
        exit(EXIT_FAILURE);
    }

    // 写入数据
    nwritten = write(fd, write_buf, strlen(write_buf));
    if (nwritten == -1) {
        perror("write failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("Wrote %zd bytes: %s\n", nwritten, write_buf);

    // 将文件指针移到文件开头(否则读取不到刚写入的数据)
    off_t offset = lseek(fd, 0, SEEK_SET);
    if (offset == -1) {
        perror("lseek failed");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 读取数据
    nread = read(fd, read_buf, sizeof(read_buf) - 1); // 留一个字节给'\0'
    if (nread == -1) {
        perror("read failed");
        close(fd);
        exit(EXIT_FAILURE);
    } else if (nread == 0) {
        printf("Reached end of file\n");
    } else {
        read_buf[nread] = '\0'; // 加上字符串结束符
        printf("Read %zd bytes: %s\n", nread, read_buf);
    }

    close(fd);
    return 0;
}

先向文件写入一段字符串,然后使用 lseek 系统调用将文件指针移到文件开头,再读取文件内容并打印。需要注意的是,write 调用返回的实际写入字节数可能小于请求的字节数,因此在实际应用中可能需要循环写入,直到所有数据都被写入。同样,read 调用也可能需要循环读取,直到读取到预期的数据量或到达文件末尾。​

四、文件定位(lseek 系统调用)​

在对文件进行读写操作时,系统会维护一个文件偏移量(file offset),也称为文件指针,它指示了下一次读写操作开始的位置。lseek 系统调用用于修改这个文件偏移量,实现对文件的随机访问。​

4.1 函数原型

代码语言:javascript

AI代码解释

#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
4.2 参数说明​

  • fd:文件描述符,标识要操作的文件。​
  • offset:偏移量,是一个有符号整数,表示要移动的字节数。​
  • whence:用于指定偏移量的参考位置,有以下三种取值:​
    • SEEK_SET:将文件偏移量设置为 offset 字节(从文件开头开始计算)。​
    • SEEK_CUR:将文件偏移量设置为当前偏移量加上 offset 字节。​
    • SEEK_END:将文件偏移量设置为文件末尾加上 offset 字节(offset 为负时表示向文件开头方向移动)。​
4.3 返回值​
  • 成功:返回新的文件偏移量(从文件开头开始计算的字节数)。​
  • 失败:返回 - 1,并设置 errno 指示错误类型,如 EBADF(文件描述符无效)、ESPIPE(文件是管道、FIFO 或套接字,这些文件不支持定位操作)等。​
4.4 使用示例

代码语言:javascript

AI代码解释

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd;
    char buf[100];
    off_t offset;

    // 创建并打开一个文件
    fd = open("seektest.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open failed");
        exit(EXIT_FAILURE);
    }

    // 写入一些数据
    const char *data = "0123456789abcdefghijklmnopqrstuvwxyz";
    write(fd, data, strlen(data));

    // 将文件指针移到开头
    offset = lseek(fd, 0, SEEK_SET);
    if (offset == -1) {
        perror("lseek to beginning failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("Moved to beginning, offset: %ld\n", (long)offset);

    // 读取5个字节
    ssize_t nread = read(fd, buf, 5);
    if (nread == -1) {
        perror("read failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    buf[nread] = '\0';
    printf("Read %zd bytes: %s\n", nread, buf);

    // 从当前位置向后移动10个字节
    offset = lseek(fd, 10, SEEK_CUR);
    if (offset == -1) {
        perror("lseek from current failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("Moved 10 bytes from current, new offset: %ld\n", (long)offset);

    // 读取5个字节
    nread = read(fd, buf, 5);
    if (nread == -1) {
        perror("read failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    buf[nread] = '\0';
    printf("Read %zd bytes: %s\n", nread, buf);

    // 从文件末尾向前移动20个字节
    offset = lseek(fd, -20, SEEK_END);
    if (offset == -1) {
        perror("lseek from end failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("Moved 20 bytes before end, new offset: %ld\n", (long)offset);

    // 读取5个字节
    nread = read(fd, buf, 5);
    if (nread == -1) {
        perror("read failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    buf[nread] = '\0';
    printf("Read %zd bytes: %s\n", nread, buf);

    close(fd);
    return 0;
}

展示了 lseek 系统调用的三种使用方式:从文件开头设置偏移量、从当前位置调整偏移量、从文件末尾调整偏移量。通过 lseek,我们可以灵活地定位到文件的任意位置进行读写操作,实现随机访问。​

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐