mknod函数 链接到标题

好的,我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 mknod 函数,它用于创建特殊文件,如设备文件(字符设备和块设备)和 FIFO(命名管道)。


1. 函数介绍 链接到标题

mknod 是一个 Linux 系统调用,用于创建特殊文件节点。这些特殊文件包括:

  1. 字符设备文件 (Character Device Files): 提供对字符流设备的访问,数据按顺序读写,不经过缓冲区。例如,终端 (/dev/tty), 串口 (/dev/ttyS0)。
  2. 块设备文件 (Block Device Files): 提供对块设备的访问,数据以固定大小的块进行读写,通常有缓冲。例如,硬盘 (/dev/sda), CD-ROM (/dev/cdrom)。
  3. FIFO (Named Pipes): 用于进程间通信的命名管道,允许无亲缘关系的进程进行单向数据传输。
  4. Unix 域套接字 (在某些系统上): 用于同一主机上进程间通信的套接字文件。

mknod 是系统管理员和设备驱动程序开发者的重要工具。它允许创建与硬件设备或内核功能相对应的文件系统入口点。普通用户通常不需要直接使用 mknod,因为设备文件通常在系统安装或设备插拔时由系统自动创建。

重要: 创建设备文件需要适当的权限(通常是 root 权限),因为这涉及到与底层硬件和内核的交互。


2. 函数原型 链接到标题

#include <sys/stat.h>    // 必需 (包含 mode_t, dev_t 等)
#include <sys/sysmacros.h> // 必需 (包含 makedev 等宏,glibc 2.26+)
#include <unistd.h>      // 必需

int mknod(const char *pathname, mode_t mode, dev_t dev);

3. 功能 链接到标题

  • 创建特殊文件: 根据 mode 参数指定的类型,在文件系统中创建一个名为 pathname 的特殊文件节点。
  • 指定设备信息: 对于设备文件,使用 dev 参数指定主设备号 (major number) 和次设备号 (minor number),这两个号码唯一标识了系统中的一个硬件设备或内核驱动。

4. 参数 链接到标题

  • const char *pathname: 指向一个以空字符 (\0) 结尾的字符串,该字符串包含了要创建的特殊文件的路径名。这可以是相对路径或绝对路径。
  • mode_t mode: 指定要创建的文件类型和权限。
    • 文件类型 (必须指定以下之一):
      • S_IFCHR: 创建字符设备文件。
      • S_IFBLK: 创建块设备文件。
      • S_IFIFO: 创建 FIFO (命名管道)。
      • S_IFSOCK: 创建 Unix 域套接字(在支持的系统上)。
    • 文件权限: 与 chmod 类似,可以使用八进制数(如 0666)或符号常量(如 S_IRUSR | S_IWUSR)来指定文件的访问权限。常见的权限包括 0666 (所有用户读写) 或 0644 (所有者读写,组和其他用户只读)。
    • 组合示例:
      • S_IFCHR | 0666: 创建权限为 0666 的字符设备文件。
      • S_IFIFO | 0644: 创建权限为 0644 的 FIFO。
  • dev_t dev: 指定设备号。这个参数对于 FIFO 和套接字会被忽略。
    • 对于字符设备和块设备: 这个参数是必需的,它包含了主设备号和次设备号。
    • 创建 dev_t: 通常使用 <sys/sysmacros.h> 中的 makedev(major, minor) 宏来从主设备号 (major) 和次设备号 (minor) 创建 dev_t 值。
      • 主设备号 (Major Number): 标识设备的类型或驱动程序。例如,IDE 磁盘的主设备号通常是 3。
      • 次设备号 (Minor Number): 标识特定类型下的具体设备实例。例如,/dev/hda (主设备号 3, 次设备号 0) 和 /dev/hdb (主设备号 3, 次设备号 64)。
    • 获取设备号: 可以使用 ls -l 命令查看现有设备文件的主次设备号。例如,ls -l /dev/null 会显示 crw-rw-rw- 1 root root 1, 3 ...,其中 1 是主设备号,3 是次设备号。

5. 返回值 链接到标题

  • 成功时: 返回 0。
  • 失败时:
    • 返回 -1,并设置全局变量 errno 来指示具体的错误原因:
      • EACCES: 搜索路径名中的某个目录被拒绝,或父目录不支持写入。
      • EDQUOT: 用户的磁盘配额已满。
      • EEXIST: pathname 指定的文件已存在。
      • EFAULT: pathname 指针指向进程地址空间之外。
      • EINVAL: mode 参数无效(例如,没有指定有效的文件类型位)。
      • ELOOP: 解析 pathname 时遇到符号链接环。
      • ENAMETOOLONG: 路径名过长。
      • ENOENT: 路径名前缀中的某个目录不存在。
      • ENOMEM: 内核内存不足。
      • ENOSPC: 设备上没有空间来创建新的目录项。
      • ENOTDIR: 路径名前缀不是一个目录。
      • EPERM: 调用进程没有权限创建特殊文件。这是最常见的错误,因为创建设备文件通常需要 root 权限。
      • EROFS: 路径名存在于只读文件系统上。

6. 相似函数,或关联函数 链接到标题

  • mkfifo(const char *pathname, mode_t mode): 专门用于创建 FIFO (命名管道) 的简化函数。它等价于 mknod(pathname, S_IFIFO | mode, 0)
  • mkdir(const char *pathname, mode_t mode): 用于创建目录。
  • creat(const char *pathname, mode_t mode): 用于创建普通文件(已废弃,推荐使用 open())。
  • stat, lstat: 用于检查文件状态,包括确定文件是否为特殊文件以及获取其设备号。
  • major(dev_t dev), minor(dev_t dev): 用于从 dev_t 值中提取主设备号和次设备号。

7. 示例代码 链接到标题

示例 1:创建 FIFO (命名管道) 链接到标题

这个例子演示了如何使用 mknod 创建一个 FIFO,并展示其基本用法。

#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>

// 使用 mknod 创建 FIFO
int create_fifo_with_mknod(const char *pathname, mode_t mode) {
    printf("使用 mknod 创建 FIFO: %s\n", pathname);
    // S_IFIFO 表示创建 FIFO
    // dev 参数对于 FIFO 被忽略,通常设为 0
    if (mknod(pathname, S_IFIFO | mode, 0) == -1) {
        perror("mknod 创建 FIFO 失败");
        return -1;
    }
    printf("成功创建 FIFO: %s\n", pathname);
    return 0;
}

// 使用 mkfifo 创建 FIFO (推荐方式)
int create_fifo_with_mkfifo(const char *pathname, mode_t mode) {
    printf("使用 mkfifo 创建 FIFO: %s\n", pathname);
    if (mkfifo(pathname, mode) == -1) {
        perror("mkfifo 创建 FIFO 失败");
        return -1;
    }
    printf("成功创建 FIFO: %s\n", pathname);
    return 0;
}

// FIFO 写入端
void fifo_writer(const char *fifo_path) {
    int fd;
    const char *message = "Hello from FIFO writer!";

    printf("FIFO 写入端启动...\n");
    // 以只写模式打开 FIFO (会阻塞直到有读取端打开)
    fd = open(fifo_path, O_WRONLY);
    if (fd == -1) {
        perror("打开 FIFO 写入端失败");
        return;
    }
    printf("FIFO 写入端已连接\n");

    // 写入数据
    if (write(fd, message, strlen(message)) == -1) {
        perror("写入 FIFO 失败");
    } else {
        printf("成功写入消息: %s\n", message);
    }

    close(fd);
    printf("FIFO 写入端关闭\n");
}

// FIFO 读取端
void fifo_reader(const char *fifo_path) {
    int fd;
    char buffer[256];
    ssize_t bytes_read;

    printf("FIFO 读取端启动...\n");
    // 以只读模式打开 FIFO (会阻塞直到有写入端打开)
    fd = open(fifo_path, O_RDONLY);
    if (fd == -1) {
        perror("打开 FIFO 读取端失败");
        return;
    }
    printf("FIFO 读取端已连接\n");

    // 读取数据
    bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read == -1) {
        perror("读取 FIFO 失败");
    } else {
        buffer[bytes_read] = '\0';
        printf("成功读取消息: %s\n", buffer);
    }

    close(fd);
    printf("FIFO 读取端关闭\n");
}

int main() {
    const char *fifo_path = "test_fifo";

    printf("=== FIFO (命名管道) 创建和使用演示 ===\n");

    // 1. 使用 mknod 创建 FIFO
    if (create_fifo_with_mknod(fifo_path, 0666) == -1) {
        // 如果失败,尝试使用 mkfifo (推荐)
        if (create_fifo_with_mkfifo(fifo_path, 0666) == -1) {
            exit(EXIT_FAILURE);
        }
    }

    // 2. 创建子进程进行通信演示
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork 失败");
        unlink(fifo_path);
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程作为写入端
        sleep(1); // 让父进程先打开读取端
        fifo_writer(fifo_path);
        exit(EXIT_SUCCESS);
    } else {
        // 父进程作为读取端
        fifo_reader(fifo_path);
        
        // 等待子进程结束
        wait(NULL);
        
        // 清理 FIFO 文件
        if (unlink(fifo_path) == -1) {
            perror("删除 FIFO 失败");
        } else {
            printf("已删除 FIFO: %s\n", fifo_path);
        }
    }

    return 0;
}

代码解释:

  1. 定义了两个创建 FIFO 的函数:
    • create_fifo_with_mknod: 使用 mknod 创建 FIFO,展示了 S_IFIFO 标志的使用。
    • create_fifo_with_mkfifo: 使用专门的 mkfifo 函数,这是创建 FIFO 的推荐方式。
  2. fifo_writer 函数模拟 FIFO 的写入端,以 O_WRONLY 模式打开 FIFO 并写入消息。
  3. fifo_reader 函数模拟 FIFO 的读取端,以 O_RDONLY 模式打开 FIFO 并读取消息。
  4. main 函数首先创建 FIFO,然后使用 fork 创建子进程。父进程作为读取端,子进程作为写入端,演示了 FIFO 的基本通信机制。
  5. 最后通过 unlink 删除 FIFO 文件。

示例 2:创建字符设备和块设备文件 链接到标题

这个例子演示了如何使用 mknod 创建字符设备和块设备文件。注意:这需要 root 权限,并且需要知道正确的主次设备号。

#define _GNU_SOURCE
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

// 打印设备文件信息
void print_device_info(const char *pathname) {
    struct stat sb;
    if (stat(pathname, &sb) == -1) {
        perror("stat");
        return;
    }

    if (S_ISCHR(sb.st_mode)) {
        printf("字符设备文件: %s\n", pathname);
        printf("  主设备号: %d\n", major(sb.st_rdev));
        printf("  次设备号: %d\n", minor(sb.st_rdev));
        printf("  权限: %04o\n", sb.st_mode & 0777);
    } else if (S_ISBLK(sb.st_mode)) {
        printf("块设备文件: %s\n", pathname);
        printf("  主设备号: %d\n", major(sb.st_rdev));
        printf("  次设备号: %d\n", minor(sb.st_rdev));
        printf("  权限: %04o\n", sb.st_mode & 0777);
    } else {
        printf("不是设备文件: %s\n", pathname);
    }
}

// 创建字符设备文件
int create_char_device(const char *pathname, int major_num, int minor_num, mode_t mode) {
    dev_t dev = makedev(major_num, minor_num);
    
    printf("创建字符设备文件: %s\n", pathname);
    printf("  主设备号: %d\n", major_num);
    printf("  次设备号: %d\n", minor_num);
    printf("  权限: %04o\n", mode);
    
    // S_IFCHR 表示创建字符设备文件
    if (mknod(pathname, S_IFCHR | mode, dev) == -1) {
        printf("创建失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  权限不足。创建设备文件通常需要 root 权限。\n");
        }
        return -1;
    }
    
    printf("成功创建字符设备文件\n");
    print_device_info(pathname);
    return 0;
}

// 创建块设备文件
int create_block_device(const char *pathname, int major_num, int minor_num, mode_t mode) {
    dev_t dev = makedev(major_num, minor_num);
    
    printf("创建块设备文件: %s\n", pathname);
    printf("  主设备号: %d\n", major_num);
    printf("  次设备号: %d\n", minor_num);
    printf("  权限: %04o\n", mode);
    
    // S_IFBLK 表示创建块设备文件
    if (mknod(pathname, S_IFBLK | mode, dev) == -1) {
        printf("创建失败: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("  权限不足。创建设备文件通常需要 root 权限。\n");
        }
        return -1;
    }
    
    printf("成功创建块设备文件\n");
    print_device_info(pathname);
    return 0;
}

// 演示常见设备的主次设备号
void show_common_devices() {
    printf("\n=== 常见设备的主次设备号 ===\n");
    printf("注意: 这些信息可能因系统和内核版本而异\n");
    printf("字符设备:\n");
    printf("  /dev/null      - 主: 1, 次: 3\n");
    printf("  /dev/zero      - 主: 1, 次: 5\n");
    printf("  /dev/random    - 主: 1, 次: 8\n");
    printf("  /dev/urandom   - 主: 1, 次: 9\n");
    printf("  /dev/tty       - 主: 5, 次: 0\n");
    printf("  /dev/console   - 主: 5, 次: 1\n");
    printf("  /dev/ptmx      - 主: 5, 次: 2\n");
    printf("\n块设备:\n");
    printf("  /dev/ram0      - 主: 1, 次: 0 (RAM disk)\n");
    printf("  /dev/loop0     - 主: 7, 次: 0 (回环设备)\n");
    printf("  /dev/fd0       - 主: 2, 次: 0 (软盘)\n");
    printf("  /dev/sda       - 主: 8, 次: 0 (第一块 SCSI/SATA 硬盘)\n");
    printf("  /dev/sda1      - 主: 8, 次: 1 (第一块硬盘的第一个分区)\n");
    printf("\n");
}

int main(int argc, char *argv[]) {
    printf("=== 设备文件创建演示 ===\n");
    printf("当前用户 UID: %d\n", getuid());
    printf("当前用户 EUID: %d\n", geteuid());
    
    if (geteuid() != 0) {
        printf("警告: 创建设备文件通常需要 root 权限。\n");
        printf("以下操作可能会失败。\n\n");
    }
    
    show_common_devices();

    // 演示创建一些常见的虚拟设备文件
    printf("=== 创建测试设备文件 ===\n");
    
    // 1. 创建 /dev/null 的副本 (字符设备)
    printf("1. 尝试创建字符设备 (类似 /dev/null)\n");
    create_char_device("test_null", 1, 3, 0666);
    
    // 2. 创建 /dev/zero 的副本 (字符设备)
    printf("\n2. 尝试创建字符设备 (类似 /dev/zero)\n");
    create_char_device("test_zero", 1, 5, 0666);
    
    // 3. 创建 RAM disk 的副本 (块设备)
    printf("\n3. 尝试创建块设备 (类似 /dev/ram0)\n");
    create_block_device("test_ram", 1, 0, 0660);
    
    // 4. 展示已创建的设备文件信息
    printf("\n=== 已创建的设备文件信息 ===\n");
    if (access("test_null", F_OK) == 0) {
        print_device_info("test_null");
    }
    if (access("test_zero", F_OK) == 0) {
        print_device_info("test_zero");
    }
    if (access("test_ram", F_OK) == 0) {
        print_device_info("test_ram");
    }
    
    // 5. 清理测试文件
    printf("\n=== 清理测试文件 ===\n");
    const char *test_files[] = {"test_null", "test_zero", "test_ram"};
    for (int i = 0; i < 3; i++) {
        if (access(test_files[i], F_OK) == 0) {
            if (unlink(test_files[i]) == -1) {
                printf("删除 %s 失败: %s\n", test_files[i], strerror(errno));
            } else {
                printf("已删除 %s\n", test_files[i]);
            }
        }
    }
    
    printf("\n=== 总结 ===\n");
    printf("mknod 使用要点:\n");
    printf("1. 创建字符设备: mknod(pathname, S_IFCHR | mode, makedev(major, minor))\n");
    printf("2. 创建块设备:   mknod(pathname, S_IFBLK | mode, makedev(major, minor))\n");
    printf("3. 创建 FIFO:    mknod(pathname, S_IFIFO | mode, 0) (推荐使用 mkfifo)\n");
    printf("4. 需要 root 权限\n");
    printf("5. 需要知道正确的主次设备号\n");
    printf("6. 现代系统通常自动管理设备文件\n");
    
    return 0;
}

代码解释:

  1. print_device_info 函数使用 statmajor/minor 宏来显示设备文件的详细信息。
  2. create_char_devicecreate_block_device 函数封装了使用 mknod 创建字符设备和块设备的逻辑。
  3. show_common_devices 函数列出了一些常见设备的主次设备号作为参考。
  4. main 函数演示了创建几种虚拟设备文件的过程,并在最后清理创建的文件。
  5. 代码包含了详细的错误处理和权限检查信息。

示例 3:错误处理和权限检查 链接到标题

这个例子重点演示 mknod 可能遇到的各种错误情况及其处理。

#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

// 演示各种 mknod 错误情况
void demonstrate_mknod_errors() {
    printf("=== Mknod 错误处理演示 ===\n");
    
    // 1. 权限不足错误 (最常见的错误)
    printf("\n1. 权限不足错误 (EPERM)\n");
    printf("   尝试在当前目录创建设备文件...\n");
    if (mknod("no_permission_dev", S_IFCHR | 0666, makedev(1, 3)) == -1) {
        printf("   错误: %s\n", strerror(errno));
        if (errno == EPERM) {
            printf("   说明: 普通用户通常没有权限创建设备文件\n");
            printf("   解决: 使用 sudo 或以 root 身份运行\n");
        }
    }
    
    // 2. 文件已存在错误
    printf("\n2. 文件已存在错误 (EEXIST)\n");
    // 先创建一个普通文件
    FILE *fp = fopen("existing_file", "w");
    if (fp) {
        fprintf(fp, "test");
        fclose(fp);
        printf("   先创建文件: existing_file\n");
    }
    
    // 尝试用 mknod 覆盖已存在的文件
    printf("   尝试用 mknod 创建同名设备文件...\n");
    if (mknod("existing_file", S_IFCHR | 0666, makedev(1, 3)) == -1) {
        printf("   错误: %s\n", strerror(errno));
        if (errno == EEXIST) {
            printf("   说明: 文件已存在,mknod 不会覆盖\n");
            printf("   解决: 先删除文件或使用不同的名称\n");
        }
    }
    unlink("existing_file");
    
    // 3. 无效的 mode 参数
    printf("\n3. 无效的 mode 参数 (EINVAL)\n");
    printf("   尝试使用无效的文件类型...\n");
    // 故意不指定文件类型标志
    if (mknod("invalid_mode", 0666, makedev(1, 3)) == -1) {
        printf("   错误: %s\n", strerror(errno));
        if (errno == EINVAL) {
            printf("   说明: mode 参数必须包含有效的文件类型标志\n");
            printf("   解决: 使用 S_IFCHR, S_IFBLK, S_IFIFO 等\n");
        }
    }
    
    // 4. 路径不存在
    printf("\n4. 路径不存在 (ENOENT)\n");
    printf("   尝试在不存在的目录中创建文件...\n");
    if (mknod("/nonexistent/path/device", S_IFCHR | 0666, makedev(1, 3)) == -1) {
        printf("   错误: %s\n", strerror(errno));
        if (errno == ENOENT) {
            printf("   说明: 路径中的某个目录不存在\n");
            printf("   解决: 确保路径中的所有目录都存在\n");
        }
    }
    
    // 5. 只读文件系统
    printf("\n5. 只读文件系统 (EROFS)\n");
    printf("   尝试在 /proc 中创建设备文件...\n");
    if (mknod("/proc/test_device", S_IFCHR | 0666, makedev(1, 3)) == -1) {
        printf("   错误: %s\n", strerror(errno));
        if (errno == EROFS) {
            printf("   说明: 不能在只读文件系统上创建文件\n");
        }
    }
    
    // 6. 路径名过长
    printf("\n6. 路径名过长 (ENAMETOOLONG)\n");
    char long_path[2000];
    memset(long_path, 'a', sizeof(long_path) - 1);
    long_path[sizeof(long_path) - 1] = '\0';
    // 将中间某个位置设为 '/'
    long_path[100] = '/';
    printf("   尝试使用过长的路径名...\n");
    if (mknod(long_path, S_IFCHR | 0666, makedev(1, 3)) == -1) {
        printf("   错误: %s\n", strerror(errno));
        if (errno == ENAMETOOLONG) {
            printf("   说明: 路径名超过了系统限制\n");
        }
    }
    
    printf("\n");
}

// 演示正确的设备文件使用方式
void demonstrate_proper_usage() {
    printf("=== 正确的设备文件使用方式 ===\n");
    
    printf("1. 系统管理场景:\n");
    printf("   - 通常由 udev 或 systemd 在设备插拔时自动创建\n");
    printf("   - 系统安装时由安装脚本创建\n");
    printf("   - 手动创建时需要 root 权限和正确的设备号\n\n");
    
    printf("2. 开发测试场景:\n");
    printf("   - 创建测试用的 FIFO 进行进程间通信\n");
    printf("   - 使用回环设备 (loop device) 挂载磁盘镜像\n");
    printf("   - 创建 RAM disk 进行性能测试\n\n");
    
    printf("3. 推荐做法:\n");
    printf("   - 对于 FIFO: 优先使用 mkfifo()\n");
    printf("   - 对于设备文件: 依赖系统自动管理\n");
    printf("   - 手动创建时: 仔细核对主次设备号\n");
    printf("   - 权限设置: 遵循最小权限原则\n\n");
}

// 演示设备号操作
void demonstrate_device_numbers() {
    printf("=== 设备号操作演示 ===\n");
    
    // 常见设备的设备号
    struct {
        const char *name;
        int major;
        int minor;
        const char *type;
    } devices[] = {
        {"null", 1, 3, "字符设备"},
        {"zero", 1, 5, "字符设备"},
        {"random", 1, 8, "字符设备"},
        {"full", 1, 7, "字符设备"},
        {"ram0", 1, 0, "块设备"},
        {"loop0", 7, 0, "块设备"},
        {"sda", 8, 0, "块设备"}
    };
    
    printf("常见设备的主次设备号:\n");
    for (int i = 0; i < 7; i++) {
        dev_t dev = makedev(devices[i].major, devices[i].minor);
        printf("  %-8s: 主=%2d, 次=%2d (%s) -> dev_t=0x%lx\n", 
               devices[i].name, devices[i].major, devices[i].minor, 
               devices[i].type, (unsigned long)dev);
        
        // 演示从 dev_t 提取主次设备号
        int major_num = major(dev);
        int minor_num = minor(dev);
        printf("           提取: 主=%2d, 次=%2d\n", major_num, minor_num);
    }
    
    printf("\n");
}

int main() {
    printf("当前进程信息:\n");
    printf("  实际 UID: %d\n", getuid());
    printf("  有效 UID: %d\n", geteuid());
    printf("  实际 GID: %d\n", getgid());
    printf("  有效 GID: %d\n", getegid());
    printf("\n");
    
    demonstrate_mknod_errors();
    demonstrate_device_numbers();
    demonstrate_proper_usage();
    
    printf("=== 总结 ===\n");
    printf("mknod 常见错误类型:\n");
    printf("  EPERM: 权限不足(最常见)\n");
    printf("  EEXIST: 文件已存在\n");
    printf("  EINVAL: 无效的 mode 参数\n");
    printf("  ENOENT: 路径不存在\n");
    printf("  EROFS: 只读文件系统\n");
    printf("  ENAMETOOLONG: 路径名过长\n\n");
    
    printf("使用建议:\n");
    printf("  1. 创建 FIFO 优先使用 mkfifo()\n");
    printf("  2. 创建设备文件需要 root 权限\n");
    printf("  3. 确保使用正确的主次设备号\n");
    printf("  4. 现代系统推荐依赖 udev 自动管理\n");
    printf("  5. 注意错误处理和权限检查\n");
    
    return 0;
}

代码解释:

  1. demonstrate_mknod_errors 函数逐一演示了 mknod 可能遇到的各种典型错误。
  2. demonstrate_proper_usage 函数总结了 mknod 的正确使用场景和推荐做法。
  3. demonstrate_device_numbers 函数展示了如何使用 makedevmajorminor 宏来操作设备号。
  4. main 函数协调整个演示过程,并在最后提供总结。

编译和运行:

# 编译示例
gcc -o mknod_example1 mknod_example1.c
gcc -o mknod_example2 mknod_example2.c
gcc -o mknod_example3 mknod_example3.c

# 运行示例 (注意权限要求)
# 示例1: FIFO 演示
./mknod_example1

# 示例2: 设备文件演示 (需要 root 权限)
sudo ./mknod_example2

# 示例3: 错误处理演示
./mknod_example3

总结:

mknod 函数是 Linux 系统编程中用于创建特殊文件节点的重要工具。虽然普通应用程序很少直接使用它,但理解其工作原理对于系统管理、设备驱动开发和底层编程至关重要。在使用时必须注意权限要求、正确的设备号以及适当的错误处理。对于 FIFO,推荐使用更简单的 mkfifo 函数。