Linux编程:1、文件编程

一、Linux 文件编程与 C 语言文件编程的区别

特性 C 语言 I/O 库函数 Linux 文件编程(系统调用)
实现层面 用户空间(glibc 库) 内核空间(系统调用)
跨平台性 跨平台(Windows/Linux) 仅限 Linux 系统
性能 带用户空间缓冲区,默认高效 需手动管理缓冲区,使用得当更快
底层依赖 依赖系统调用(如 Linux 的 open) 直接操作内核 API

核心差异:

  • C 语言函数是系统调用的封装,例如fopen底层调用open。
  • 系统调用适合底层控制(如文件锁、内存映射),库函数适合通用文件操作。

二、文件描述符(File Descriptor, FD)

1. 基本概念
  • 作用:Linux 中对文件(含设备)的唯一标识,通过整数(FD)操作文件。
  • 进程关联:每个进程维护独立的 FD 表,记录打开的文件。
2. 预定义描述符
FD 宏定义 用途
0 STDIN_FILENO 标准输入
1 STDOUT_FILENO 标准输出
2 STDERR_FILENO 标准错误输出
3. 范围与限制
  • 默认范围 :0 ~ OPEN_MAX-1。
  • 查看系统限制 :cat /proc/sys/fs/file-max。
  • 修改限制 (需 root):echo 2048 > /proc/sys/fs/file-max。
4. 用法示例
  • Shell 脚本中使用 FD :

    cpp 复制代码
    FILE *fp = fopen("data.txt", "r");
    int fd = fileno(fp);  // 转换为文件描述符

三、文件操作基础

1. 打开文件(open)
  • 函数原型:

    cpp 复制代码
    int open(const char *pathname, int flags);          // 无权限模式
    int open(const char *pathname, int flags, mode_t mode); // 带权限模式
  • 头文件 :#include <fcntl.h>, #include <sys/stat.h>。
  • 关键参数 :
    • flags:必选其一(O_RDONLY/O_WRONLY/O_RDWR),可组合其他标志:
      • O_CREAT:文件不存在时创建。
      • O_EXCL:与O_CREAT联用,若文件存在则打开失败。
      • O_APPEND:写入时追加到文件末尾。
    • mode:新文件权限(如0644表示 rw-r--r--),需与O_CREAT配合使用。
  • 返回值:成功返回 FD(非负整数),失败返回 - 1。

示例:创建并读写文件

cpp 复制代码
int fd = open("data.txt", O_RDWR | O_CREAT, 0600); // 0600表示所有者可读写
if (fd < 0) { perror("open failed"); }
2. 创建文件(creat)
  • 等价于 :open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode)。
  • 函数原型 :int creat(const char *pathname, mode_t mode);。
3. 关闭文件(close)
  • 函数原型 :int close(int fd);。
  • 注意:不关闭可能导致 FD 耗尽,影响后续打开文件。

四、读写文件

1. 读文件(read)
  • 函数原型 :

    cpp 复制代码
    ssize_t read(int fd, void *buf, size_t count);
  • 返回值 :

    • 成功:实际读取字节数(可能小于count,如文件末尾)。
    • 失败:-1,设置errno。
  • 性能优化:避免频繁小尺寸读取,利用页缓存(4KB 为单位)。

2. 写文件(write)
  • 函数原型 :

    cpp 复制代码
    ssize_t write(int fd, const void *buf, size_t count);
  • 返回值:成功返回实际写入字节数,失败返回 - 1。

  • 示例:追加写入

    cpp 复制代码
    int fd = open("data.txt", O_WRONLY | O_APPEND);
    write(fd, "Hello World!", 12);
3. C 语言与系统调用性能对比
  • 场景 :写入 100 万次数据。

    • C 语言(fwrite) :用户空间缓冲区优化,耗时约 0.02 秒。

      cpp 复制代码
      #include <stdio.h>
      #include <stdlib.h>
      
      #define MAX 1000000
      
      int main(void)
      {
          FILE* fp = fopen("data1", "wb");
          if (fp == NULL) {
              perror("fopen failed.");
              exit(1);
          }
          for (int i = 0; i < MAX; i++) {
              fwrite(&i, sizeof(int), 1, fp);
          }
          fclose(fp);
          return 0;
      }
    • 系统调用(write) :无用户缓冲区,耗时约 0.18 秒,需手动添加缓冲区优化。

      cpp 复制代码
      #include <fcntl.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      
      #define MAX 1000000
      
      int main(void)
      {
          int fd = open("data2", O_RDWR | O_CREAT | O_TRUNC, 0666);
          if (fd == -1) {
              perror("open file failed.");
              exit(1);
          }
          for (int i = 0; i < MAX; i++) {
              write(fd, &i, sizeof(int));
          }
          close(fd);
          return 0;
      }
    • 原因:
      系统函数在用户层没有缓冲区,在内核层有缓冲区,但是缓冲区很小。所以大量数据在写入文件时,频繁刷新缓冲区降低了写入速率。
      (缓冲区满了才真正写入) 所以系统函数(如 write)需要加自定义缓冲区以提高速率, 而标准 C 函数(如 fwrite)在用户层有默认的缓冲区,所以案例一比案例二更快

  • 改进系统调用的代码:

    cpp 复制代码
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    #define MAX 1000000
    #define BUFF_SIZE 512
    
    int main(void)
    {
        int fd = open("data2", O_RDWR | O_CREAT | O_TRUNC, 0666);
        if (fd == -1) {
            perror("open file failed.");
            exit(1);
        }
        int buffer[BUFF_SIZE];
        for (int i = 0; i < MAX; i++) {
            buffer[i % BUFF_SIZE] = i;
            if ((i + 1) % BUFF_SIZE == 0) {
                write(fd, buffer, sizeof(buffer));
            }
        }
        close(fd);
        return 0;
    }

结论:在使用系统函数接口时,如果自定义了一个合适的缓冲区,会使速度显著提升


五、文件偏移量与定位(lseek)

  • 作用:调整文件读写位置(字节偏移量)。

  • 函数原型 :

    cpp 复制代码
    off_t lseek(int fd, off_t offset, int whence);
  • whence参数 :

    • SEEK_SET:从文件开头偏移offset。
    • SEEK_CUR:从当前位置偏移offset(可正可负)。
    • SEEK_END:从文件末尾偏移offset(通常为负数)。
  • 示例:修改文件指定位置内容

    cpp 复制代码
    int fd = open("test.txt", O_RDWR);
    lseek(fd, 5, SEEK_SET);  // 定位到第6字节(索引从0开始)
    write(fd, "ABC", 3);     // 覆盖写入

六、文件状态与元数据(stat系列函数)

  • 作用:获取文件类型、权限、大小、时间戳等信息。

  • 相关函数 :

    函数 参数 说明
    stat 路径名 获取文件或符号链接指向的文件信息
    lstat 路径名 获取符号链接本身的信息
    fstat 文件描述符 通过 FD 获取文件信息
  • 关键结构体 :

    cpp 复制代码
    struct stat {
      mode_t st_mode;   // 文件类型(如`S_ISDIR`判断目录)
      off_t st_size;    // 文件大小(字节)
      ino_t st_ino;     // inode节点号(唯一标识)
      time_t st_mtime;  // 最后修改时间
    };

示例:判断文件类型

cpp 复制代码
struct stat status;
stat("test.txt", &status);
if (S_ISREG(status.st_mode)) { printf("普通文件\n"); }

七、硬链接与软链接

1. 硬链接(Hard Link)
  • 本质:多个文件名指向同一 inode,共享物理数据。
  • 特点 :
    • 不能跨文件系统,不能链接目录。
    • 删除任意硬链接不影响其他链接,仅当所有硬链接删除后文件才被删除。
  • 创建命令 :ln <源文件> <链接名>。
2. 软链接(符号链接,Symbolic Link)
  • 本质:独立文件,存储目标文件路径。
  • 特点 :
    • 可跨文件系统,可链接目录。
    • 源文件删除后成为 "死链接",访问时报错。
  • 创建命令 :ln -s <源文件或目录> <链接名>。
3. 对比表格
特性 硬链接 软链接
文件类型 与源文件相同(普通文件) 特殊文件(类型为l)
跨文件系统 不支持 支持
源文件删除影响 无(仅硬链接数减 1) 失效(死链接)
存储空间 共享 inode,不占用额外空间 存储路径,占用少量空间

八、文件锁机制

1. 作用

避免多进程并发访问文件导致的数据竞争,分为建议性锁 和强制性锁。

2. 核心函数(fcntl)
cpp 复制代码
#include <unistd.h>
#include <fcntl.h>
#include <sys/file.h>

int fcntl(int fd, int cmd, struct flock *lock);
  • cmd参数 :

    • F_SETLK:设置锁(非阻塞,失败直接返回)。
    • F_GETLK:查询锁状态。
  • struct flock结构体 :

    cpp 复制代码
    struct flock {
      short l_type;   // 锁类型(F_RDLCK读锁/F_WRLCK写锁/F_UNLCK解锁)
      off_t l_start;  // 偏移量(配合l_whence)
      off_t l_len;    // 加锁长度(0表示到文件末尾)
    };
3. 建议性锁(默认)
  • 特点:不强制阻止访问,需进程主动检查锁状态。

  • 示例:检测写锁

    cpp 复制代码
    struct flock lock = {.l_type = F_WRLCK, .l_whence = SEEK_SET};
    fcntl(fd, F_GETLK, &lock);
    if (lock.l_pid != -1) { printf("文件被进程%d锁定\n", lock.l_pid); }
4. 强制性锁
  • 启用条件 :
    1. 挂载文件系统时添加mand选项:sudo mount -o remount,mand /。
    2. 设置文件权限为g+s且取消组执行权限:chmod g+s,g-x test.txt。
  • 特点:不兼容的操作会被阻塞(如读锁存在时写操作阻塞)。

九、内存映射(mmap)

1. 作用

将文件数据映射到进程地址空间,直接通过指针操作文件,减少内核与用户空间的数据拷贝。

2. 核心函数
cpp 复制代码
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length); // 解除映射
  • 关键参数 :
    • prot:权限(如PROT_READ | PROT_WRITE)。
    • flags:
      • MAP_SHARED:修改会写回文件,可被其他进程共享。
      • MAP_PRIVATE:修改仅在当前进程可见,不影响原文件。
    • offset:必须为 4KB(4096 字节)的整数倍。
3. 优势与场景
  • 优势 :
    • 减少read/write系统调用的拷贝开销(仅 1 次拷贝)。
    • 简化随机访问(直接操作指针)。
  • 适用场景 :
    • 大文件随机读写。
    • 多进程共享文件数据(通过MAP_SHARED)。
  • 缺点 :
    • 不适合频繁写操作或变长文件。

4. 示例:读取文件内容

cpp 复制代码
int fd = open("test.txt", O_RDONLY);
off_t len = lseek(fd, 0, SEEK_END);
char *buf = (char*)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
printf("%s", buf); // 直接打印映射内容
munmap(buf, len);

十、关键快捷键与命令

  • man命令 :查看帮助文档
    • man 2 open:查看系统调用open的手册。
    • man -f open:查看open所属章节。
  • man快捷键 :
    • Space:下翻一页;b:上翻一页;/字符串:搜索。
相关推荐
2035去旅行4 小时前
bootcmd 详细讲解
linux·嵌入式·arm·soc
陈希1235 小时前
Linux 防火墙配置实战:firewalld、iptables 与云安全组
linux·运维·服务器
女神下凡5 小时前
芯参谋(95):嵌入式文件系统_开发指南
linux·服务器·arm开发·驱动开发·设计规范
且从容.9 小时前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道9 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
且从容.12 小时前
ubuntu安装flink
linux·ubuntu·flink
爱和冰阔落14 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨14 小时前
WSL2的安装与使用
linux·wsl
微小的鱼14 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng14 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器