【Linux基础IO篇】系统文件接口

【Linux基础IO篇】系统文件接口

作者:爱写代码的刚子

时间:2023.11.1

前言:本篇博客是关于C语言文件接口的回顾,以及学习在Linux系统下关于文件的系统调用接口。


回顾C语言的文件接口

在本篇博客不详细介绍,可以参考我之前写的一篇博客:

文件操作有关知识

注意:C默认会打开三个输入输出流,分别是stdin,stdout,stderr,这三个流类型都是FILE* ,fopen返回值类型:文件指针

系统文件I/O

用系统接口模拟上面的文件接口

写入文件:

在这里插入图片描述

在这里插入图片描述

读取文件:

在这里插入图片描述

在这里插入图片描述

open接口的介绍

在这里插入图片描述

pathname:要打开或者要创建的目标文件

flag:打开文件时,可以传入多个参数选项,用一个或多个常量进行’或’运算,构成flags

参数

  • O_RDONLY: 只读打开
  • O_WRONLY: 只写打开
  • O_RDWR : 读,写打开

这三个常量,必须指定一个且只能指定一个

  • O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
  • O_APPEND: 追加写
  • O_TRUNC:将文件原本的内容全部丢弃,文件大小变为0。

返回值

  • 成功:新打开的文件描述符
  • 失败:-1

我们可以参照flags参数的形式自己编写一个类似效果的代码:


#define ONE (1<<0) // 1
#define TWO (1<<1) // 2
#define THREE (1<<2) // 4
#define FOUR (1<<3) // 8

void show(int flags)
{
    if(flags&ONE) printf("hello function1\n");
    if(flags&TWO) printf("hello function2\n");
    if(flags&THREE) printf("hello function3\n");
    if(flags&FOUR) printf("hello function4\n");
}

int main()
{
    printf("-----------------------------\n");
    show(ONE);
    printf("-----------------------------\n");
    show(TWO);
    printf("-----------------------------\n");

    show(ONE|TWO);
    printf("-----------------------------\n");
    show(ONE|TWO|THREE);
    printf("-----------------------------\n");
    show(ONE|THREE);
    printf("-----------------------------\n");
    show(THREE|FOUR);
    printf("-----------------------------\n");
}

注意使用第三个函数参数mode_t mode时,需要考虑当前的权限掩码,如果需要屏蔽权限掩码,则需要将当前进程的掩码设为0(umask(0);)

在这里插入图片描述

st_mode也用到了mode_t类型的变量.

  • open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默权限。

  • write、read、close、lseek,类比C文件相关接口

open函数返回值

  • 系统调用和库函数,C语言中的库函数对系统调用接口进行了一系列封装,(如:fopen、fclose、fread、fwrite统称为库函数libc)

  • 而,open、close、read、write、lseek都属于系统调用接口

在这里插入图片描述

f#系列的函数,都是对系统调用函数的封装。

文件描述符fd(小整数)

0 & 1 & 2

  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.

  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件

在这里插入图片描述

在这里插入图片描述

文件描述符的分配规则

实验(图片中的perror中的内容应该为open,当时写错了):

在这里插入图片描述

以上图片中我们关闭了显示器一号文件,再打开test.c,运行程序

在这里插入图片描述

在这里插入图片描述

以上结果中我们发现本来应该要向显示器显示的数据却打印到了文件中,说明新打开的文件将1作为了当前文件的文件描述符

结论在files_struct数组中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

重定向

在这里插入图片描述

在这里插入图片描述

将本来应该输出到显示器的文件写入到文件中

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

重定向本质:

在这里插入图片描述

dup2系统调用

在这里插入图片描述

在这里插入图片描述
解释为什么’\n’不能刷新缓冲区,而是必须使用fflush函数手动刷新

  • 标准输出它本身是行缓冲,本来反斜杠N可以刷新,但是现在把它重定向到另外一个文件当中了,但普通文件是全缓冲,行缓冲就不生效了,所以说’\n‘就没用,flush是把它重新再刷新一下,全部的缓冲区就能刷出来了。

在这里插入图片描述

printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1 下标所表示内容,已经变成了myfile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写 入,进而完成输出重定向。

改进myshell,添加重定向功能

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <fcntl.h>

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44

#define NONE -1
#define IN_RDIR     0
#define OUT_RDIR    1
#define APPEND_RDIR 2

int lastcode = 0;
int quit = 0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];
char pwd[LINE_SIZE];
char *rdirfilename = NULL;
int rdir = NONE;

// 自定义环境变量表
char myenv[LINE_SIZE];
// 自定义本地变量表


const char *getusername()
{
    return getenv("USER");
}

const char *gethostname1()
{
    return getenv("HOSTNAME");
}

void getpwd()
{
    getcwd(pwd, sizeof(pwd));
}

void check_redir(char *cmd)
{

    // ls -al -n
    // ls -al -n >/</>> filename.txt
    char *pos = cmd;
    while(*pos)
    {
        if(*pos == '>')
        {
            if(*(pos+1) == '>'){
                *pos++ = '\0';
                *pos++ = '\0';
                while(isspace(*pos)) pos++;
                rdirfilename = pos;
                rdir=APPEND_RDIR;
                break;
            }
            else{
                *pos = '\0';
                pos++;
                while(isspace(*pos)) pos++;
                rdirfilename = pos;
                rdir=OUT_RDIR;
                break;
            }
        }
        else if(*pos == '<')
        {
            *pos = '\0'; // ls -a -l -n < filename.txt
            pos++;
            while(isspace(*pos)) pos++;
            rdirfilename = pos;
            rdir=IN_RDIR;
            break;
        }
        else{
            //do nothing
        }
        pos++;
    }
}


void interact(char *cline, int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname1(), pwd);
    char *s = fgets(cline, size, stdin);
    assert(s);
    (void)s;
    // "abcd\n\0"
    cline[strlen(cline)-1] = '\0';

    //ls -a -l > myfile.txt
    check_redir(cline);
}

int splitstring(char cline[], char *_argv[])
{
    int i = 0;
    argv[i++] = strtok(cline, DELIM);
    while(_argv[i++] = strtok(NULL, DELIM)); // 是=不是==
    return i - 1;
}

void NormalExcute(char *_argv[])
{
    pid_t id = fork();
    if(id < 0){
        perror("fork");
        return;
    }
    else if(id == 0){
        int fd = 0;

        // 做了重定向的工作,后面在进行程序替换的时候并不影响
        if(rdir == IN_RDIR)
        {
            fd = open(rdirfilename, O_RDONLY);
            dup2(fd, 0);
        }
        else if(rdir == OUT_RDIR)
        {
            fd = open(rdirfilename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if(rdir == APPEND_RDIR)
        {
            fd = open(rdirfilename, O_CREAT|O_WRONLY|O_APPEND, 0666);
            dup2(fd, 1);
        }
        //让子进程执行命令
        //execvpe(_argv[0], _argv, environ);
        execvp(_argv[0], _argv);
        exit(EXIT_CODE);
    }
    else{
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid == id) 
        {
            lastcode = WEXITSTATUS(status);
        }
    }
}

int buildCommand(char *_argv[], int _argc)
{
    if(_argc == 2 && strcmp(_argv[0], "cd") == 0){
        chdir(argv[1]);
        getpwd();
        sprintf(getenv("PWD"), "%s", pwd);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0], "export") == 0){
        strcpy(myenv, _argv[1]);
        putenv(myenv);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){
        if(strcmp(_argv[1], "$?") == 0)
        {
            printf("%d\n", lastcode);
            lastcode=0;
        }
        else if(*_argv[1] == '$'){
            char *val = getenv(_argv[1]+1);
            if(val) printf("%s\n", val);
        }
        else{
            printf("%s\n", _argv[1]);
        }

        return 1;
    }

    // 特殊处理一下ls
    if(strcmp(_argv[0], "ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}

int main()
{
    while(!quit){
        // 1.
        rdirfilename = NULL;
        rdir = NONE;
        // 2. 交互问题,获取命令行, ls -a -l > myfile / ls -a -l >> myfile / cat < file.txt
        interact(commandline, sizeof(commandline));

        // commandline -> "ls -a -l -n\0" -> "ls" "-a" "-l" "-n"
        // 3. 子串分割的问题,解析命令行
        int argc = splitstring(commandline, argv);
        if(argc == 0) continue;

        // 4. 指令的判断 
        // debug
        //for(int i = 0; argv[i]; i++) printf("[%d]: %s\n", i, argv[i]);
        //内键命令,本质就是一个shell内部的一个函数
        int n = buildCommand(argv, argc);

        // 5. 普通命令的执行
        if(!n) NormalExcute(argv);
    }
    return 0;
}

在这里插入图片描述

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱写代码的刚子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值