Linux之基础IO文件系统讲解(中)

简介: 文件描述符的分配规则看下面代码

文件描述符的分配规则

看下面代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
  int fd = open("myfile", O_RDONLY);
  if(fd < 0){
    perror("open");
    return 1;
  }
  printf("fd: %d\n", fd);
  close(fd);
  return 0;
}

输出发现是 fd: 3

关闭0或者2,再看

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
  close(0);
  //close(2);
  int fd = open("myfile", O_RDONLY);
  if(fd < 0){
    perror("open");
    return 1;
  }
  printf("fd: %d\n", fd);
  close(fd);
  return 0;
}

发现是结果是: fd: 0 或者 fd: 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的

最小的一个下标,作为新的文件描述符

C标准库文件操作函数简易模拟实现

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <stdlib.h>
struct MyFILE_{
    int fd;
    char buffer[1024];
    int end; //当前缓冲区的结尾
};
typedef struct MyFILE_ MyFILE;
MyFILE *fopen_(const char *pathname, const char *mode)
{
    assert(pathname);
    assert(mode);
    MyFILE *fp = NULL;
    if(strcmp(mode, "r") == 0)
    {
    }
    else if(strcmp(mode, "r+") == 0)
    {
    }
    else if(strcmp(mode, "w") == 0)
    {
        int fd = open(pathname, O_WRONLY | O_TRUNC | O_CREAT, 0666);
        if(fd >= 0)
        {
            fp = (MyFILE*)malloc(sizeof(MyFILE));
            memset(fp, 0, sizeof(MyFILE));
            fp->fd = fd;
        }
    }
    else if(strcmp(mode, "w+") == 0)
    {
    }
    else if(strcmp(mode, "a") == 0)
    {
    }
    else if(strcmp(mode, "a+") == 0)
    {
    }
    else{}
    return fp;
}
void fputs_(const char *message, MyFILE *fp)
{
    assert(message);
    assert(fp);
    strcpy(fp->buffer+fp->end, message); 
    fp->end += strlen(message);
    printf("%s\n", fp->buffer);
    if(fp->fd == 0)
    {
        //标准输入
    }
    else if(fp->fd == 1)
    {
        //标准输出
        if(fp->buffer[fp->end-1] =='\n' )
        {
            write(fp->fd, fp->buffer, fp->end);
            fp->end = 0;
        }
    }
    else if(fp->fd == 2)
    {
        //标准错误
    }
    else
    {
        //其他文件
    }
}
void fflush_(MyFILE *fp)
{
    assert(fp);
    if(fp->end != 0)
    {
        //把数据写到了内核
        write(fp->fd, fp->buffer, fp->end);
        syncfs(fp->fd); //将数据写入到磁盘
        fp->end = 0;
    }
}
void fclose_(MyFILE *fp)
{
    assert(fp);
    fflush_(fp);
    close(fp->fd);
    free(fp);
}
  1. fopen_
  • 这个函数模拟了 C 标准库中的 fopen 函数。根据给定的文件路径和打开模式,创建并返回一个 MyFILE 结构体指针。根据模式不同,可以选择以只写、只读等方式打开文件。
  • MyFILE 结构体包含了一个文件描述符 fd,一个缓冲区 buffer,以及 end 表示当前缓冲区的结尾位置。
  • 当以写入模式打开文件时,会调用系统的 open 函数,分配并初始化一个 MyFILE 结构体,用于后续的文件写入。
  1. fputs_
  • 这个函数模拟了 C 标准库中的 fputs 函数。它将给定的字符串写入到 MyFILE 结构体的缓冲区中,然后根据文件描述符的不同,选择是否将缓冲区中的数据写入文件。
  • 在写入标准输出时,会检查缓冲区的内容,如果末尾是换行符,则执行实际的写入操作,并清空缓冲区。
  1. fflush_
  • 这个函数模拟了 C 标准库中的 fflush 函数。它将 MyFILE 结构体缓冲区中的数据写入文件,并使用 syncfs 函数将数据同步到磁盘。
  1. fclose_
  • 这个函数模拟了 C 标准库中的 fclose 函数。它首先调用 fflush_ 函数,确保缓冲区数据写入文件,然后关闭文件描述符,并释放分配的 MyFILE 结构体内存。

需要注意的是,这段代码是一个简化版本的模拟,实际的 C 标准库文件操作更加复杂,并且在实际应用中会涉及更多的细节和错误处理。此代码示例提供了一个简单的思路,用于理解文件操作的基本原理。

重定向

看下面的代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
  close(1);
  int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
  if(fd < 0){
    perror("open");
    return 1;
  }
  printf("fd: %d\n", fd);
  fflush(stdout);
  close(fd);
  exit(0);
}

此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <

那重定向的本质是什么呢?

dup2 系统调用

在 Linux 中,dup2 是一个系统调用,用于创建一个文件描述符的副本,并将副本连接到另一个文件描述符。它的原型如下:

int dup2(int oldfd, int newfd);

其中,oldfd 是现有的文件描述符,而 newfd 是你想要创建的新文件描述符。调用 dup2(oldfd, newfd) 会将 newfd 关联到 oldfd 所指向的文件,就像 newfd 是通过 open 或其他方式创建的一样。

具体来说,dup2 调用的作用是将文件描述符 newfd 关闭(如果 newfd 已经打开),然后复制 oldfd 的所有属性(包括文件状态标志、文件偏移量等),最终将 newfdoldfd 指向的文件相连接。这意味着对于 newfd 的任何读取或写入操作都会影响到与 oldfd 相关联的文件。

dup2 的典型用途之一是重定向标准输入、标准输出或标准错误流。通过将某个文件描述符与标准输入、标准输出或标准错误的文件描述符(0、1、2)连接,可以实现输入输出的重定向。

例如,以下代码片段将标准输出重定向到一个文件:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
    int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    // 使用 dup2 将文件描述符 1(标准输出)重定向到 fd
    if (dup2(fd, 1) < 0) {
        perror("dup2");
        return 1;
    }
    // 现在标准输出将写入到 output.txt 文件
    printf("This will be written to output.txt\n");
    close(fd);
    return 0;
}

这段代码中,dup2(fd, 1) 将文件描述符 1(标准输出)重定向到 fd,使得后续的标准输出都会写入到 “output.txt” 文件中。

在minishell中添加重定向功能

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>  // 添加头文件以支持 waitpid 函数
#define MAX_CMD 1024
char command[MAX_CMD];
// 获取用户输入命令
int do_face() {
    memset(command, 0x00, MAX_CMD);
    printf("minishell$ ");
    fflush(stdout);
    // 使用 scanf 读取用户输入,遇到换行符为止
    if (scanf("%[^\n]%*c", command) == 0) {
        getchar();
        return -1;
    }
    return 0;
}
// 解析命令行输入,将输入命令分解成参数列表
char **do_parse(char *buff) {
    int argc = 0;
    static char *argv[32];  // 最多支持 32 个参数
    char *ptr = buff;
    while (*ptr != '\0') {
        if (!isspace(*ptr)) {
            argv[argc++] = ptr;
            while ((!isspace(*ptr)) && (*ptr) != '\0') {
                ptr++;
            }
        } else {
            while (isspace(*ptr)) {
                *ptr = '\0';  // 将空白字符替换为字符串结束符
                ptr++;
            }
        }
    }
    argv[argc] = NULL;  // 参数列表以 NULL 结尾
    return argv;
}
// 处理重定向操作
int do_redirect(char *buff) {
    char *ptr = buff, *file = NULL;
    int type = 0, fd, redirect_type = -1;
    while (*ptr != '\0') {
        if (*ptr == '>') {
            *ptr++ = '\0';
            redirect_type++;
            if (*ptr == '>') {
                *ptr++ = '\0';
                redirect_type++;
            }
            while (isspace(*ptr)) {
                ptr++;
            }
            file = ptr;
            while ((!isspace(*ptr)) && *ptr != '\0') {
                ptr++;
            }
            *ptr = '\0';
            if (redirect_type == 0) {
                fd = open(file, O_CREAT | O_TRUNC | O_WRONLY, 0664);
            } else {
                fd = open(file, O_CREAT | O_APPEND | O_WRONLY, 0664);
            }
            dup2(fd, 1);  // 将标准输出重定向到文件
        }
        ptr++;
    }
    return 0;
}
// 执行命令
int do_exec(char *buff) {
    char **argv = {NULL};
    int pid = fork();  // 创建子进程
    if (pid == 0) {  // 子进程中执行命令
        do_redirect(buff);
        argv = do_parse(buff);
        if (argv[0] == NULL) {
            exit(-1);
        }
        execvp(argv[0], argv);  // 执行命令
    } else {  // 父进程等待子进程执行结束
        waitpid(pid, NULL, 0);
    }
    return 0;
}
int main(int argc, char *argv[]) {
    while (1) {
        if (do_face() < 0)
            continue;
        do_exec(command);  // 执行用户输入的命令
    }
    return 0;
}

这段代码实现了一个基本的交互式命令行解释器(shell)。它允许用户输入命令,并在子进程中执行这些命令。以下是各个函数的功能解释:

  1. do_face() 函数:
  • 该函数用于显示命令提示符,读取用户输入的命令。
  • 使用 scanf 函数读取用户输入的一行命令,并将其存储在 command 缓冲区中。
  1. do_parse() 函数:
  • 该函数用于解析命令行输入,将输入命令分解成参数列表。
  • 通过遍历输入的字符,将非空白字符作为参数的起始位置,并将参数分割为单独的字符串。
  • 参数列表会存储在 argv 数组中,每个元素都指向一个参数字符串,最后一个元素为 NULL
  1. do_redirect() 函数:
  • 该函数用于处理重定向操作,将标准输出重定向到指定文件。
  • 在命令字符串中寻找 > 符号,根据符号后的内容判断重定向的类型和目标文件名,然后使用文件操作函数打开该文件并将标准输出重定向到该文件。
  1. do_exec() 函数:
  • 该函数用于执行解析后的命令。
  • 使用 fork 创建子进程,子进程中调用 do_redirect() 进行重定向,然后使用 execvp() 函数执行命令。
  1. main() 函数:
  • 主函数使用一个无限循环,等待用户输入命令并执行。
  • 调用 do_face() 获取用户输入,并在 do_exec() 中执行命令。

这个简化的 minishell 支持基本的命令执行和标准输出重定向。它通过 forkexecvp 实现命令的执行,同时通过重定向实现标准输出的重定向。

相关文章
|
20天前
|
存储 Linux 数据处理
探索Linux操作系统的内核与文件系统
本文深入探讨了Linux操作系统的核心组件,包括其独特的内核结构和灵活的文件系统。文章首先概述了Linux内核的主要功能和架构,接着详细分析了文件系统的工作原理以及它如何支持数据存储和检索。通过比较不同的文件系统类型,本文旨在为读者提供一个关于如何根据特定需求选择合适文件系统的参考框架。
|
4天前
|
存储 Java
文件系统和IO流
文件系统和IO流
12 3
|
6天前
|
Linux C++
c++高级篇(三) ——Linux下IO多路复用之poll模型
c++高级篇(三) ——Linux下IO多路复用之poll模型
|
6天前
|
缓存 监控 网络协议
c++高级篇(二) ——Linux下IO多路复用之select模型
c++高级篇(二) ——Linux下IO多路复用之select模型
|
6天前
|
Linux
Linux异步io机制 io_uring
Linux异步io机制 io_uring
13 1
|
18天前
|
存储 缓存 Unix
【嵌入式软件工程师面经】Linux文件IO
【嵌入式软件工程师面经】Linux文件IO
21 1
|
6天前
|
存储 JSON Linux
探索Linux文件系统的奥秘:`lsblk`命令详解
`lsblk`是Linux下用于列出块设备详情的命令,显示设备名、大小、类型、挂载点等信息,尤其适合查看磁盘分区和挂载状态。它以树形结构展示设备间的依赖,且支持多种输出格式。常用参数如`-a`显示所有设备,`-f`显示文件系统信息,`-o`定制输出列。结合其他命令使用能有效管理文件系统。注意权限和输出格式选择。
|
22天前
|
存储 缓存 Linux
【Linux】文件系统
【Linux】文件系统
|
22天前
|
Linux
45. 【Linux教程】文件系统检查
45. 【Linux教程】文件系统检查
23 0
|
22天前
|
Linux
44. 【Linux教程】创建文件系统
44. 【Linux教程】创建文件系统
22 0