BIT.5_基础IO

文章介绍了Linux系统中文件操作的基本概念,包括当前路径、文件读写接口如open、read、write、close,以及重定向的本质。同时,讨论了文件描述符在进程中的作用,展示了简易的自制缓冲区实现,并提到了标准输出stdout和错误输出stderr的区别。此外,还涉及了静态库和动态库的制作与使用。
摘要由CSDN通过智能技术生成

预备知识

在这里插入图片描述

什么叫做当前路径

在这里插入图片描述

strlen(s) + 1问题

在这里插入图片描述

cat代码实现


#include <stdio.h>                                                                                    
#include <unistd.h>
#include  <string.h>
// mytextfile + 一个文件名 
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        printf("argv error!\n");
        return 1;
    }
    FILE* fp = fopen(argv[1], "r");
    if (fp == NULL)
    {
        perror("fopen");
        return 2;
    }
    // 按行读取
    char line[64];
    // fgets -> C -> s(string) -> 会自动在字符结尾添加\0
    while (fgets(line, sizeof(line), fp) != NULL)
    {
        // printf("%s", line);
        fprintf(stdout, "%s", line);
    }
    fclose(fp);

    return 0;
}

在这里插入图片描述

文件写入的系统接口细节

在这里插入图片描述

模拟实现传递标志位
// 用int中的不重复的一个bit,就可以标识一种状态
#define ONE 0x1   //0000 0001
#define TWO 0x2   //0000 0010
#define THREE 0x4 //0000 0100

void show(int flags) //0000 0101
{
    if(flags & ONE)  printf("hello one\n"); // 0000 0011 & 0000 0001
    if(flags & TWO)   printf("hello two\n");
    if(flags & THREE) printf("hello three\n");
}


int main()
{
    show(ONE);
    printf("-----------------------------------------\n");
    show(TWO);
    printf("-----------------------------------------\n");
    show(ONE | TWO); //000 0001 | 0000 0010 = 0000 0011
    printf("-----------------------------------------\n");
    show(ONE | TWO | THREE);
    printf("-----------------------------------------\n");
    show(ONE | THREE);
    printf("-----------------------------------------\n");
    return 0;
}

在这里插入图片描述
在这里插入图片描述

int main()
{
    /// 系统接口open
    
    //int fd = open("log.txt", O_RDONLY); .// 等价于 fopen("log.txt", "r")
    //int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666); // 等价于 fopen("log.txt", "w")
    /// mode 可以设置文件权限 0666 表示 rw-rw-rw- 因为有umask会覆盖一些权限  所以需要umask(0)
    umask(0);
    int fd = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); // 等价于fopen("log.txt", "a");
    printf("open success, fd: %d\n", fd);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    /// 系统接口read
    char buffer[64];
    memset(buffer, '\0', sizeof(buffer));
    read(fd, buffer, sizeof(buffer));

    /// 系统接口write
    const char *s = "hello write\n";
    const char *s = "aa\n";
    write(fd, s, strlen(s)); //要不要+1

    /// 系统接口close
    close(fd);
    return 0;
}

在这里插入图片描述

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

int main()
{
	int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-
	printf("open success, fd: %d\n", fd1);
	int fd2 = open("log2.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-
	printf("open success, fd: %d\n", fd2);
	int fd3 = open("log3.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-
	printf("open success, fd: %d\n", fd3);

	close(fd1);
	close(fd2);
	close(fd3);
	return 0;
}

文件系统函数

open

函数原型:
  当文件存在时
  int open(const char* pathname,int flags)
  当文件不存在时
  int open (const char* pathname,int flags,int perms)
  
flags
	主模式:
	O_RDONLY:只读模式
	O_WRONLY:只写模式
	O_RDWR:读写,模式

副模式:
	O_CREAT:当文件不存在,需要去创建文件
	O_APPEND:追加模式
	O_DIRECT:直接IO模式
	O_SYNC:同步模式
	O_NOBLOCK:非阻塞模式
	O_EXCL:如果同时使用O_CREAT而且该文件又已经存在时,则返回错误, 用途:以防止多个进程同时创建同一个文件
	O_TRUNC:若文件存在,则长度被截为0,属性不变

返回值:
	成功:文件描述符
	失败:-1

close

函数原型:
int close(int fd)

返回值:
	成功:0
	失败:-1

read

函数原型:

ssize_t read(int fd,void *buff,size_t count)		// ssize_t是有符号数
1
返回值:
	成功:
		count:成功读取全部字节
		0~count:
			剩余文件长度小于count
			读取期间被异步信号打断
	失败:
		-1,读取错误

write

ssize_t write(int fd,void *buff,size_t count)
1
返回值:
	成功:
		count:成功读取全部字节
		0~count:
			写入期间被异步信号打断
	失败:
		-1,读取错误

lseek

功能:
	设置文件读写位置

函数原型:
	off_t lseek(int fd,off_t offset,int whence)
	若whence为SEEK_SET,基准点为文件开头
	若whence为SEEK_CUR,基准点为当前位置
	若whence为SEEK_END,基准点为文件末尾
返回值:
	成功:文件偏移位置值
	失败:-1
读写是使用的同一个位置指针。使用O_RDWR时,如果先读了一段再写,那么写的位置从读完处开始,同理先写了一段再读,
那么读的位置从写完处开始。可以使用lseek来改变文件位置指针,即读写的位置。

sync

fd理解

在这里插入图片描述

重定向的本质

// 输出重定向(从显示器到文件)
int main()
{
    close(1);
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd); 
    printf("fd: %d\n", fd);
    printf("fd: %d\n", fd);
    printf("fd: %d\n", fd);
    printf("fd: %d\n", fd);
    printf("fd: %d\n", fd);
    fprintf(stdout, "hello fprintf\n");
    const char *s = "hello fwrite\n";
    fwrite(s, strlen(s), 1, stdout);

    fflush(stdout); 
    close(fd); 
    return 0;
}


/// dup2实现: 输出重定向
// mytextflie + 内容
// ./mytextfile hello 会将hello写入到stdout文件
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        return 1;
    }
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    dup2(fd, 1);
    fprintf(stdout, "%s\n", argv[1]); //stdout->1->显示器

    close(fd);
    return 0;
}

// 输入重定向(从键盘重定向到文件)
int main()
{
    // 本来要从键盘读取内容,会从log.txt文件读取内容,并保存到buffer中
    close(0);
    int fd = open("log.txt", O_WRONLY | O_APPEND | O_CREAT);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    printf("fd: %d\n", fd);

    char buffer[64];
    fgets(buffer, sizeof buffer, stdin);

    printf("%s\n", buffer);
    return 0;
}

在这里插入图片描述

问题

  1. 文件描述符的理解
    在进程中每打开一个文件,都会创建有相应的文件描述信息struct file,这个描述信息被添加在pcb的struct files_struct中,以数组的形式进行管理,随即向用户返回数组的下标作为文件描述符,用于操作文件
  2. 请简述重定向的实现原理
    每个文件描述符都是一个内核中文件描述信息数组的下标,对应有一个文件的描述信息用于操作文件,而重定向就是在不改变所操作的文件描述符的情况下,通过改变描述符对应的文件描述信息进而实现改变所操作的文件

缓冲区

在这里插入图片描述
在这里插入图片描述

简易的自制缓冲区

#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>

#define NUM 1024

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;
}

//是不是应该是C标准库中的实现!
void fputs_(const char* message, MyFILE* fp)
{
    assert(message);
    assert(fp);

    strcpy(fp->buffer + fp->end, message); //abcde\0
    fp->end += strlen(message);

    //for debug
    printf("%s\n", fp->buffer);

    //暂时没有刷新, 刷新策略是谁来执行的呢?用户通过执行C标准库中的代码逻辑,来完成刷新动作
    //这里效率提高,体现在哪里呢??因为C提供了缓冲区,那么我们就通过策略,减少了IO的执行次数(不是数据量)
    if (fp->fd == 0)
    {
        //标准输入
    }
    else if (fp->fd == 1)
    {
        //标准输出
        if (fp->buffer[fp->end - 1] == '\n')
        {
            //fprintf(stderr, "fflush: %s", fp->buffer); //2
            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);
}

int main()
{
    //close(1);
    MyFILE* fp = fopen_("./log.txt", "w");
    if (fp == NULL)
    {
        printf("open file error");
        return 1;
    }

    fputs_("one: hello world", fp);

    fork();

    fclose_(fp);
}

minishell支持缓冲区

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

#define NUM 1024
#define SIZE 32
#define SEP " "

//保存完整的命令行字符串
char cmd_line[NUM];

//保存打散之后的命令行字符串
char *g_argv[SIZE];

// 写一个环境变量的buffer,用来测试
char g_myval[64];

#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define NONE_REDIR 0

int redir_status = NONE_REDIR;

char *CheckRedir(char *start)
{
    assert(start);
    char *end = start + strlen(start) - 1; //ls -a -l\0
    while(end >= start)
    {
        if(*end == '>')
        {
            if(*(end-1) == '>')
            {
                redir_status = APPEND_REDIR;
                *(end-1) = '\0';
                end++;
                break;
            }
            redir_status = OUTPUT_REDIR;
            *end = '\0';
            end++;
            break;
            //ls -a -l>myfile.txt
            //ls -a -l>>myfile.txt
        }
        else if(*end == '<')
        {
            //cat < myfile.txt,输入
            redir_status = INPUT_REDIR;
            *end = '\0';
            end++;
            break;
        }
        else{
            end--;
        }
    }
    if(end >= start)
    {
        return end; //要打开的文件
    }
    else{
        return NULL;
    }
}
// shell 运行原理 : 通过让子进程执行命令,父进程等待&&解析命令
int main()
{
    extern char**environ;
    //0. 命令行解释器,一定是一个常驻内存的进程,不退出
    while(1)
    {
        //1. 打印出提示信息 [whb@localhost myshell]# 
        printf("[root@我的主机 myshell]# ");
        fflush(stdout);
        memset(cmd_line, '\0', sizeof cmd_line);
        //2. 获取用户的键盘输入[输入的是各种指令和选项: "ls -a -l -i"]
        // "ls -a -l>log.txt"
        // "ls -a -l>>log.txt"
        // "ls -a -l<log.txt"
        if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
        {
            continue;
        }
        cmd_line[strlen(cmd_line)-1] = '\0';
        // 2.1: 分析是否有重定向, "ls -a -l>log.txt" -> "ls -a -l\0log.txt"
        //"ls -a -l -i\n\0"
        char *sep = CheckRedir(cmd_line);
        //printf("echo: %s\n", cmd_line);
        //3. 命令行字符串解析:"ls -a -l -i" -> "ls" "-a" "-i"
        // export myval=105
        g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串
        int index = 1;
        if(strcmp(g_argv[0], "ls") == 0)
        {
            g_argv[index++] = "--color=auto";
        }
        if(strcmp(g_argv[0], "ll") == 0)
        {
            g_argv[0] = "ls";
            g_argv[index++] = "-l";
            g_argv[index++] = "--color=auto";
        }
        //?
        while(g_argv[index++] = strtok(NULL, SEP)); //第二次,如果还要解析原始字符串,传入NULL
        if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL)
        {
            strcpy(g_myval, g_argv[1]);
            int ret = putenv(g_myval);
            if(ret == 0) printf("%s export success\n", g_argv[1]);
            //for(int i = 0; environ[i]; i++)
            //    printf("%d: %s\n", i, environ[i]);
            continue;
        }

        //for debug
        //for(index = 0; g_argv[index]; index++)
        //    printf("g_argv[%d]: %s\n", index, g_argv[index]);
        //4.内置命令, 让父进程(shell)自己执行的命令,我们叫做内置命令,内建命令
        //内建命令本质其实就是shell中的一个函数调用
        if(strcmp(g_argv[0], "cd") == 0) //not child execute, father execute
        {
            if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd ..

            continue;
        }
        //5. fork()
        pid_t id = fork();
        if(id == 0) //child
        {
            if(sep != NULL)
            {
                int fd = -1;
                //说明命令曾经有重定向
                switch(redir_status)
                {
                    case INPUT_REDIR:
                        fd = open(sep, O_RDONLY);
                        dup2(fd, 0);
                        break;
                    case OUTPUT_REDIR:
                        fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);
                        dup2(fd, 1);
                        break;
                    case APPEND_REDIR:
                        //TODO
                        fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);
                        dup2(fd, 1);
                        break;
                    default:
                        printf("bug?\n");
                        break;
                }
            }
           // printf("下面功能让子进程进行的\n");
           // printf("child, MYVAL: %s\n", getenv("MYVAL"));
           // printf("child, PATH: %s\n", getenv("PATH"));
            //cd cmd , current child path
            //execvpe(g_argv[0], g_argv, environ); // ls -a -l -i
            //不是说好的程序替换会替换代码和数据吗??
            //环境变量相关的数据,会被替换吗??没有!
            execvp(g_argv[0], g_argv); // ls -a -l -i
            exit(1);
        }
        //father
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));
    }
}

缓冲区的行刷新/全刷新一份代码

在这里插入图片描述

stdout 和 stderr 区别和联系

在这里插入图片描述

软硬链接

在这里插入图片描述

动静态库

静态库的制作

在这里插入图片描述

静态库的使用

在这里插入图片描述

动态库的制作

在这里插入图片描述

动态库的使用

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

文件系统的理解

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值