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 实现命令的执行,同时通过重定向实现标准输出的重定向。

相关文章
|
2月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
98 0
|
2月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
105 1
Linux C/C++之IO多路复用(aio)
|
16天前
|
安全 Linux 数据安全/隐私保护
深入Linux操作系统:文件系统和权限管理
在数字世界的海洋中,操作系统是连接用户与硬件的桥梁,而Linux作为其中的佼佼者,其文件系统和权限管理则是这座桥梁上不可或缺的结构。本文将带你探索Linux的文件系统结构,理解文件权限的重要性,并通过实际案例揭示如何有效地管理和控制这些权限。我们将一起航行在Linux的命令行海洋中,解锁文件系统的奥秘,并学习如何保护你的数据免受不必要的访问。
|
4月前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
1月前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
75 8
|
1月前
|
存储 Linux 文件存储
Linux文件系统
Linux文件系统 一切皆文件 在Linux中,“一切皆文件”的概念意味着系统中的所有资源,包括硬件设备、目录及进程等,均被视为文件。这种设计简化了操作和管理,具体包括: 普通文件:存储数据的常规文件。 目录文件:包含其他文件和子目录的文件。 进程文件:在/proc目录下代表系统中运行的进程。 设备文件:位于/dev目录,代表硬件设备。 网络字节流套接字文件:用于网络通信的数据流。 链接文件:指向另一个文件的符号链接或硬链接。 管道文件:用于进程间通信的文件。
52 7
|
2月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
36 0
Linux C/C++之IO多路复用(poll,epoll)
|
2月前
|
存储 Java API
【文件IO】文件系统操作
【文件IO】文件系统操作
51 1
|
3月前
|
存储 Linux 索引
Linux 下最主流的文件系统格式——ext
【9月更文挑战第8天】硬盘被划分为若干相同大小的块(Block),默认大小为4K,便于灵活管理文件数据。文件数据分散存放于这些块中,提高了数据添加、删除和插入的便利性。
|
4月前
|
编解码 Linux 程序员
深度探索Linux操作系统 —— 构建根文件系统2
深度探索Linux操作系统 —— 构建根文件系统
50 12
下一篇
DataWorks