前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基础IO

基础IO

作者头像
code-child
发布2023-05-30 11:33:49
1490
发布2023-05-30 11:33:49
举报
文章被收录于专栏:codechildcodechildcodechild

shell执行的命令通常有两种

  1. 第三方提供的对应的在磁盘中有具体二进制文件的可执行程序(由字进程程序执行
  2. shell内部,自己实现的方法,由自己(父进程)来执行

什么叫做文件

站在系统的角度,只要是能够被读取或者能够被写出的设备都可以叫做文件。

当前路径

进程运行起来所处的路径为当前路径。

直接清空

命令行>一个为文件

系统的文件访问的接口

open

image.png
image.png

包含3个头文件 第一个参数为打开的目标文件,第二个表示打开文件时所需要的参数,参数的传入用或运算| 参数: O_RDONLY:只读打开 O_WRONLY:只写打开 O_RDWR:读写打开 以上三个常数,必须指定一个且只能指定一个 O_CREAT:若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限(注意默认的权限) O_APPEND:追加写 打开成功则返回文件的描述符 打开失败返回:-1

write

read

close

lseek

文件描述符——fd

fd就是文件描述符的小整数。 下面三个是系统默认打开的 0:键盘 1:显示器 2:显示器

文件

  1. 被进程打开的文件(内存文件)
  2. 没有被打开的文件(磁盘上,文件=内容+属性)(磁盘文件)

文件描述符的本质是数组的下标

fd的分配原则是:最小的,没有被占用的文件描述符

看下面这个代码就没有验证上面的结论

cpp#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    close(2);
    int fd=open("test.txt",O_RDWR|O_CREAT,0666);
    if(fd<0)
    perror("open");

    printf("%d\n",fd);
    close(fd);
    return 0;
}

结果输出的就是2

上面关闭的2. 当关闭1的时候,那么发生的就是重定向。

cpp#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    close(1);
    int fd=open("test.txt",O_RDWR|O_CREAT,0666);
    if(fd<0)
    perror("open");

    printf("%d\n",fd);
    fflush(stdout);
    close(fd);
    return 0;
}

我们发现1就没有在显示器上打印出来,而是写到了test.txt中 为什么会这样呢?看下面的这个图

就是因为把1号文件描述符关上之后,打开的新的文件就会占用1。导致原本可以输入到显示器中的,现在显示到文件中。 重定向的本质: 像上面那样我们还需要手动的去关闭——close(1)。其实系统提供了这样的接口 dup2

image.png
image.png
cpp#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main()
{
    int fd=open("test.txt",O_RDWR|O_CREAT,0666);
    if(fd<0)
    perror("open");
    dup2(fd,1);
    printf("hhhhhhh\n");
    close(fd);
    return 0;
}

hhhhhhh就被写到了文件中。

语言的缓存区

看代码:

cpp#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main()
{
    int fd=open("test.txt",O_RDWR|O_CREAT,0666);
    if(fd<0)
    perror("open");
    fprintf(stdout,"maole ");
    fprintf(stdout,"is cool\n");
    char str[]="emmmmmmmmmmm\n";
    //系统接口
    write(1,str,sizeof(str));
    fork();
    close(fd);
    return 0;
}
shell#当我们直接把结果打印到显示器上的时候
./myfile
#结果就是
maole is cool
emmmmmmmmmmm
#当我们把结果重定向到test.txt中的时候
#test.txt文件的内容为
emmmmmmmmmmm
maole is cool
maole is cool

为什么c语言提供的接口打印2次,而系统提供的接口打印1次? C语言它有缓存区的概念,当执行fork的时候,当代码执行完的时候,数据还没有刷新,当刷新的时候,父进程的数据就会进行写实拷贝 就会刷新到文件,而刷新到文件就是更新数据,所以要进行写实拷贝——即子进程就要对原来缓存区的数据进行拷贝。 系统的接口直接进入内核的缓存区中,此时父进程就没有数据了,那么子进程也就不能没有数据进行拷贝,那么最后的结果就只有一份数据 为什么显示到显示器上的时候就是一次呢? 因为显示到显示器中行刷新,当执行fork的时候,数据已经刷新到显示器中,fork再进行创建子进程的时候也就没缓存区的数据了。 如果把上面代码中的\n去掉的话,结果就和写到文件中是一样的,因为没有进行行刷新 看代码:

cppint main()
{
    int fd=open("test.txt",O_RDWR|O_CREAT,0666);
    if(fd<0)
    perror("open");
    fprintf(stdout,"maole ");
    fprintf(stdout,"is cool");
    char str[]="emmmmm";
    write(1,str,sizeof(str));

    fork();
    close(fd);
    return 0;
}

结果就是: emmmmmmaole is coolmaole is cool 缓存区在哪里? 缓存区就在系统的内核中,系统的内核有该结构体存储。 语言的缓存区,是语言自己封装的。

  • 为什么要用缓冲区?

缓存区只是语言上存在的

  • 缓冲区的刷新策略
  1. 立即刷新
  2. 行刷新(遇到\n
  3. 满刷新

还有一些特殊的情况:

  • 用户强制刷新:比如fflush
  • 进程退出

缓存区的存在可以提高效率,减少I/O操作 看下面这段代码

cppint main()
{
    printf("hjhgfdfghj");
    close(1);
    return 0;
}

我们发现什么都没有打印。 为什么会这样呢?

是因为在关闭标准输出之后close(1),数据还没有进入标准输出的文件之中。那么最后程序执行完毕之后,也就不会显示什么内容。

相关信息

标准输出stdout标准错误stderr都是显示到显示器上,那么他们之间有什么差别

虽然1,2都是对应的打开显示器文件,但是他们是不同的,可以认为是同一个文件被打开了两次。 看下面这个代码:

cpp#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
int main()
{
    errno=1;
    printf("stdout 1\n");
    perror("stderr 1");
    errno=2;
    printf("stdout 2\n");
    perror("stderr 2");
    return 0;
}

在命令行中输入:

image.png
image.png
  • 当运行./myfile的时候,发现都输出到显示器中了。
  • ./myfile重定向到ok.txt文件中的时候,只要标准输出的显示到文件中,标准错误的还是显示到显示器上
  • 再在后面添加2 >err.txt,就把错误的信息打印到err.txt中了

如果把所有的信息打印到同一个文件中:看下面的命令

其实对上面的命令,我们要知道它的本质——把新建fd替换1、2文件描述符

模拟实现缓存区

cpp#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>

struct MyFile_
{
    int fd;
    char buffer[1024];
    int end;
};//缓冲区
typedef struct MyFile_  MyFile;


MyFile* fopen_(const char* path,const char* mode)
{
    assert(path);
    assert(mode);

    MyFile* ret=NULL;
    if(strcmp(mode,"r")==0)
    {

    }
    else if(strcmp(mode,"w")==0)
    {

    }
    else if(strcmp(mode,"r+")==0)
    {

    }
    else if(strcmp(mode,"w+")==0)
    {
        int fd=open(path,O_WRONLY|O_CREAT,0666);
        if(fd>=0)
        {
            ret=(MyFile*) calloc(1,sizeof(MyFile));

            ret->fd=fd;
            ret->end=0;
        }
    }
    else if(strcmp(mode,"a")==0)
    {

    }
    else if(strcmp(mode,"a+")==0)
    {

    }
    else{

    }
    return ret;
}
void fputs_(const char* str,MyFile* ret)
{
    assert(ret);

    strcpy(ret->buffer+ret->end,str);
    ret->end+=strlen(str);

    if(ret->fd==0)
    {

    }
    else if(ret->fd==1)
    {
        //标准输出

        if(ret->buffer[ret->end-1]=='\n')
        {
            write(ret->fd,ret->buffer,ret->end);
            ret->end=0;
        }
    }
    else if(ret->fd==2)
    {

    }
    else{

    }

}
//刷新
void fflush_(MyFile* ret)
{
    assert(ret);
    write(ret->fd,ret->buffer,ret->end);
    //刷新到内核的缓存区
    syncfs(ret->fd);
    ret->end=0;

}
//关闭
//关闭的时候也要进行刷新
void fclose_(MyFile* ret)
{
    assert(ret);
    fflush_(ret);
    close(ret->fd);
    free(ret);

}
int main()
{
    MyFile* p=fopen_("test.txt","w+");
    fputs_("mao le id cool",p);
    fork();
    fclose_(p);
    return 0;
}

更新模拟shell

在模拟的shell中添加重定向

cpp#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define INPUT 1
#define OUTPUT 2
#define APPEND 3
#define NOOPT 0

int option=NOOPT;

char* inspaced(char* start)
{
    assert(start);

    char* end=start+strlen(start)-1;
    while(start<=end)
    {
        if(*end=='>')
        {
            option=INPUT;
            *end='\0';
            if(*(end-1)=='>')
            {
                option=APPEND;
            }
            end++;
            break;
        }
        else if(*end=='<')
        {
            option=OUTPUT;
            *end='\0';
            end++;
            break;
        }
        else
        {
            --end;
        }
    }
    if(start<=end)
    return end;
    else
    return NULL;
}


char temp[20];
int main()
{
    extern char** environ;
    //查看环境变量
    // int i=0;
    // while(environ[i])
    // {
    //     printf("%s\n",environ[i++]);
    // }
    static char* opt[100];
    static char sstr[100];

    while(1)
    {
        printf("[root@ml is cool root]# ");
        fflush(stdout);

        memset(opt,0,100);
        //scanf("%s",sstr);
        if(fgets(sstr,100,stdin)==NULL)
        continue;
        sstr[strlen(sstr)-1]='\0';
        // 检查字符是否为重定向
        char* file_name=inspaced(sstr);
        int i=0;
        char* str;
        for(str=strtok(sstr," ");str!=NULL;str=strtok(NULL," ")){
            opt[i++]=str;
        }
        if(strcmp(opt[0],"ls")==0)
        opt[i++]="--color=auto";
        // printf("i=%d,opt[0]=%s\n",i,opt[0]);
        else if(strcmp(opt[0],"ll")==0)
        {
            i=0;
            opt[i++]="ls";
            opt[i++]="-l";
            opt[i++]="--color=auto";
        }
        else if(strcmp(opt[0],"cd")==0)
        {
            if(opt[1]) chdir(opt[1]);
        }
        else if(strcmp(opt[0],"export")==0&&opt[1]!=NULL)
        {
            // strcpy(temp,opt[1]);
            // putenv(temp);
            putenv(opt[1]);
        }
        opt[i]=NULL;
        // for(i=0;opt[i];i++)
        // printf("%s\n",opt[i]);

        // sleep(100);

        pid_t id=fork();
        if(id==0)
        {
            // printf("ML:%s\n",getenv("ML"));
            // printf("PATH:\n%s\n",getenv("PATH"));
            if(file_name)
            {
                int fd=-1;
                switch(option)
                {
                    case INPUT:
                        fd=open(file_name,O_WRONLY | O_TRUNC | O_CREAT, 0666);
                        dup2(fd,1);
                        break;
                    case OUTPUT:
                        fd=open(file_name,O_CREAT|O_RDWR);
                        dup2(fd,0);
                        break;
                    case APPEND:
                        fd=open(file_name, O_WRONLY | O_APPEND | O_CREAT, 0666);
                        dup2(fd,1);
                        break;
                    default:
                    break;
                }
            }
            // execvpe(opt[0],opt,environ);
            execvp(opt[0], opt);
            exit(1);    
            
        }
        pid_t status=0;
        pid_t ret=waitpid(id,&status,0);
        if(ret)
        {
            if(WIFEXITED(status))
            {
                printf("return no error %d\n",WEXITSTATUS(status));
            }
            else{
                printf("return error %d\n",status&0x7f);
            }
        }
    }
    return 0;
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-05-26L,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么叫做文件
  • 当前路径
  • 直接清空
  • 系统的文件访问的接口
    • open
      • write
        • read
          • close
            • lseek
            • 文件描述符——fd
              • 文件
                • 语言的缓存区
                  • 模拟实现缓存区
                • 更新模拟shell
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档