进程打开文件

目录

一、预备知识

二、操作文件函数

三、操作文件系统调用

四、理解进程打开文件

函数 vs 系统调用

open的返回值 fd

如何理解一切皆文件?

理解struct file 内核对象

fd的分配规则 && 重定向

理解标准错误流(2号文件描述符)


一、预备知识

1. 文件 = 内容 + 属性, 对文件的操作无非就是对内容/属性操作

2. 内容是数据,属性也是数据,所以磁盘存储文件,既要存储文件内容,也要存储文件属性

3. C语言有打开访问关闭文件的库函数,而只有当程序运行起来时,cpu才会读取代码,执行对文件的操作,所以本质是进程访问了文件!

4. 一个进程可以打开多个文件,多个进程可以打开多个文件

5. 文件默认是在磁盘上的,加载磁盘上的文件,涉及到访问OS, 这个工作是OS进行的!

6. OS运行会打开多个文件,打开后就要对多个文件管理,先描述,再组织!

7. 文件无非分为打开了的文件和没有被打开的文件,所以对文件的学习就分为这两部分

文件打开前: 磁盘文件    文件打开后: 将文件加载到了内存中

8.研究内存中的文件的本质就是研究进程和被打开文件的关系!

二、操作文件函数

这属于C语言的知识点,我们快速回顾一下

打开文件,写入文件,关闭文件

新建写与追加写

文件以"w"方式打开,之前的内容会被清空,所以之前讲的 > log.txt 输出重定向就可以清空文件

文件以"a"方式打开,会从文件结尾开始写入, 不会清空文件内容

三、操作文件系统调用

进程打开文件涉及到了访问磁盘,所以必定是借助了OS的能力,而OS不相信任何用户,所以打开文件的函数底层一定封装了系统调用!

参数含义:

1. pathname: 打开的文件名

2. flags: 标志位 (可以传递各种宏的任意组合)

eg:

3. mode 表示打开文件指定的权限(开始的权限,文件最终权限还要去掉权限掩码)

返回值:

fd表示文件描述符,可以唯一标识文件

参数含义:

fd就是open的返回值,表示文件描述符,buf是要写入文件的字符串的起始地址,count表示要写入文件的字节数

返回值:

返回成功写入文件的字节个数

代码演示

上述所传的参数,是从头开始覆盖式地写入,文件内容并不会清空

要想打开文件后,原始内容清空,需要额外传递 O_TRUNC 参数

想要追加式写入,需要传递宏: O_APPEND

四、理解进程打开文件

函数 vs 系统调用

open的返回值 fd

当文件被打开后,就要从磁盘中加载到内存中,并且OS为了管理内存中的文件,要先描述,后组织,于是有了描述文件的结构体对象,同时进程启动后,有描述进程的task_struct结构体, 但是多个进程可能打开多个文件,如何知道哪个进程打开了哪个文件??

task_struct 中有一个指针变量,指向了一个结构体对象,结构体对象中有一个成员数组,这个数组中就存储了描述文件的结构体对象的地址,于是进程和文件本质还是解耦的,但是可以关联了!

而数组中存储的下标就叫做文件描述符!!

当调用write系统调用时,传递的id就是数组下标,这样进程就可以根据数组下标找到描述文件的结构体对象,找到文件并向文件写入了!

上面的数组下标是从3开始的,0, 1, 2下标呢??

一个程序运行起来之后,默认会打开三个流

标准输入  键盘     stdin     0

标准输出  显示器  stdout  1

标准错误  显示器  stderr   2

由上述操作文件的原理,我们知道了操作系统访问文件,只认文件描述符!!! 而C语言函数fopen返回值类型FILE*中的FILE是一个文件指针,FILE是一个C语言提供的结构体类型,FILE中必定封装了文件描述符!所以C语言的文件操作函数不仅实现方法进行了封装,返回值也进行了封装!!!

OS为啥要默认把三个流打开呢???  之前写printf就可以直接打印消息到显示器上,scanf直接从键盘读取数据,Linux下一切皆文件, 而显示器文件和键盘文件我们并没有打开,所以默认打开的流,是为了让程序员进行输入输出代码编写!

如何理解一切皆文件?

所有文件核心工作都是输入输出,本质就是读写,而有的输入输出设备是只有读,或只有写,或读写都有,硬件的访问方式是不一样的,但是Linux下一切皆文件,所以一切设备的方法无非读写,这样就可以以统一的视角看待硬件,这其实就是多态!!!

理解struct file 内核对象

文件被打开后要加载到内存,文件=内容+属性,所以strcut file中一定要有指向属性和内容的字段, 用户读写文件的本质就是文件缓冲区和用户缓冲区的数据之间的拷贝!

fd的分配规则 && 重定向

现象:

结论: fd分配规则是寻找最小的,没有被使用的数据的位置,分配给指定的文件!

如上图所示,当关闭了标准输出流(1号文件描述符)之后,再次打开新的文件,根据fd分配规则,新的文件对应的文件描述符就是1了,所以此时数组1号下标的位置会被填充描述log.txt文件的结构体的地址,此时底层就发生了狸猫换太子的操作,但是用户并不知道,当你printf/fprintf 打印内容时,虽然printf底层封装的是stdout, 但本质OS只认文件描述符,而此时文件描述符1对应的内容已经变了,所以本来应该打印到显示器上的内容输出到了文件中,这就叫做输出重定向!

同样的,当0号文件描述符被关闭之后,再从stdin中读取数据,此时就变成了从log.txt中读数据了! 本来应该从键盘读取数据变成了从文件中读取数据,这叫叫做输入重定向!

上述重定向都要先close(文件描述符), 而OS提供了系统调用,使得我们不用关闭文件描述符,只需要拷贝覆盖就能完成重定向!所以重定向的本质就是文件描述符表中数组内容的拷贝!

dup2是系统调用,参数的含义是将oldfd指向的内容拷贝覆盖到newfd指向的内容!

支持重定向功能的自己实现的命令行解释器

上篇博客我们实现了一个命令行解释器bash, 但是不支持命令行级别的重定向功能!理解了重定向之后就可以把该功能加进来了!

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


#define NUM 1024 //命令行提示符个数限制
#define SIZE 64 //命令行提示符个数限制
#define SEP " " //strtok分隔符
//#define Debug 1  //对代码实现动态裁剪

//redir
#define NoneRedir   0
#define OutputRedir 1 
#define AppendRedir 2 
#define InputRedir  3 

int redir = NoneRedir;
char *filename = NULL;

char cwd[1024];
char enval[1024]; //for test
int lastcode = 0; //最近一个进程的退出码


const char* getUsername() //获取用户名
{
    const char* name = getenv("USER");
    if(name) return name;
    else return "none";
}

const char* getHostname() //获取主机名
{
    const char* hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "none";

}

const char* getCwd() //获取当前路径
{
    const char* cwd = getenv("PWD");
    if(cwd) return cwd;
    else return "none";
}

char* homepath() //获取家目录
{
    char* home = getenv("HOME");
    if(home) return home;
    else return (char*)".";
}

int getUserCommand(char* command, int num)
{
    //输出命令行提示符
    printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd()); //获取环境变量

    //char *fgets(char *s, int size, FILE *stream);
    char* r = fgets(command, num, stdin); //从键盘获取用户指令, 用户最终一定会输入回车符(\n)

    if(r == NULL) return -1;

    //这里不会越界,因为当用户至少都要输入\n
    command[strlen(command)-1] = '\0'; //去掉用户最后输入的\n
    return strlen(command);
}

void commandSplit(char* in, char* out[])
{

    int argc = 0;
    //char *strtok(char *str, const char *delim);
    out[argc++] = strtok(in, SEP);
    while(out[argc++] = strtok(NULL, SEP));

#ifdef Debug    
    for(int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
#endif
}


void cd(const char* path)
{
    //int chdir(const char *path); 哪个进程调用chdir, 哪个进程的当前路径就会被修改!
    chdir(path);

    //char cwd[1024]; //不能写在此处,因为cd调用完后空间就释放了,环境变量就不是永久有效的了!
    
    char tmp[1024];
    //char *getcwd(char *buf, size_t size);
    getcwd(tmp, sizeof(tmp)); //获取当前进程的绝对路径!
   
    //int sprintf(char *str, const char *format, ...); 将本来应该打印到屏幕上的字符串格式化写入到str指向的空间中
    sprintf(cwd, "PWD=%s", tmp); //将tmp以"PWD=%s"的格式写入到cwd中
    
    putenv(cwd); //将cwd环境变量导入到当前进程的环境变量表中
}

//内建命令就是bash自己执行的类似于自己内部的一个函数!
//1->yes, 0->no,-1->err
int doBulidin(char* argv[])
{
    if(strcmp(argv[0], "cd") == 0)
    {
        char* path = NULL;
        if(argv[1] == NULL) path = homepath();
        else path = argv[1];
        cd(path);
        return 1;
    }
    else if(strcmp(argv[0], "export") == 0) 
    {
        if(argv[1] == NULL) return 1;
        strcpy(enval, argv[1]);//必须定义一个全局enval数组,否则导入环境变量之后,执行其他命令之后,usercommand就会重新覆盖写入,导入的环境变量就没了
        putenv(enval);
        return 1;
    }
    else if(strcmp(argv[0], "echo") == 0)
    {
        if(argv[1] == NULL) 
        {
            printf("\n");
            return 1;
        }
        if((*argv[1]) == '$' && strlen(argv[1]) > 1)
        {
            char* val= argv[1] + 1; //echo $PATH    argv[1]是$, argv[1]+1是PATH的首地址
            if(strcmp(val, "?") == 0)
            {
                printf("%d\n", lastcode);
                lastcode = 0;
            }
            else
            {
                const char* enval = getenv(val);
                if(enval) printf("%s\n", enval);
                else printf("\n");
            }
            return 1;
        }
        else
        {
            printf("%s\n", argv[1]);
            return 1;
        }
    }
    //还有其他内建命令往后加分支语句即可!!
    return 0;
}

int execute(char* argv[])
{
    pid_t id = fork();
    if(id < 0) return -1;
    else if(id == 0) 
    {
        //程序替换不会影响重定向!!
        int fd = 0;
        if(redir == InputRedir)
        {
            fd = open(filename, O_RDONLY);
            dup2(fd, 0);
        }
        else if(redir == OutputRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if(redir == AppendRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
            dup2(fd, 1);
        }
        else
        {
            //DO Nonthing
        }
        //child
        //exec command
        //int execvp(const char *file, char *const argv[]); 
        execvp(argv[0], argv);
        exit(1);
    }
    else
    {
        //father
        int status = 0;
        pid_t rid = waitpid(id, &status, 0); 
        if(rid > 0) {
            lastcode = WEXITSTATUS(status);
        };
    }
    return 0;
}


#define SkipSpace(pos) do{ while(isspace(*pos)) pos++; } while(0)

void checkRedir(char usercommand[], int len)
{
    //ls -a -l < log.txt
    //ls -a -l >> log.txt
    char* end = usercommand + len - 1;
    char* start = usercommand;
    while(end > start)
    {
        if(*end == '>')
        {
            if(*(end-1) == '>') // >> 
            { 
                *(end-1) = '\0';
                filename = end + 1;
                SkipSpace(filename);
                redir = AppendRedir;
                break;
            }
            else
            {
                *end = '\0';
                filename = end + 1;
                SkipSpace(filename);
                redir = OutputRedir;
                break;
            }
        }
        else if(*end == '<')
        {
            *end = '\0';
            filename = end+1;
            SkipSpace(filename); //如果有空格,就跳过
            redir = InputRedir;
            break;
        }
        else
        {
            end--;
        }
    }
}

int main()
{
    while(1)
    {
        redir = NoneRedir;
        filename = NULL;
        //1. 打印提示符&&获取命令行输入
        char usercommand[NUM];
        int n = getUserCommand(usercommand, sizeof(usercommand));
        if(n <= 0) continue;
        //printf("%s", usercommand);
        //" ls -a -l > log.txt " -> 判断 -> "ls -a -l"  redir_type  "log.txt"
        //1.1检查是否发生了重定向
        checkRedir(usercommand, strlen(usercommand));

        //2.对字符串做切割
        char* argv[SIZE];
        commandSplit(usercommand, argv);

        //3.检查命令是否内建命令
        n = doBulidin(argv); 
        if(n) continue; //内建命令由父进程直接执行,不用创建子进程

        //4.创建子进程执行对应的命令
        execute(argv);
    }
    return 0;
}

理解标准错误流(2号文件描述符)

现象:

为啥要标准错误流呢??

有的消息是常规消息,有的消息是错误消息,有了标准错误流,通过重定向,我们就可以将常规消息和错误消息分开,放在两个不同的文件中,方便排查错误信息!!!

C++中的cout和cerr也是同样的道理!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/450817.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

得帆助力大族激光主数据平台建设,用数据为企业生产力赋能

本期客户 大族激光科技产业集团股份有限公司&#xff08;以下简称“大族激光”&#xff09;是一家从事工业激光加工设备与自动化等配套设备及其关键器件的研发、生产、销售&#xff0c;激光、机器人及自动化技术在智能制造领域的系统解决方案的优质提供商&#xff0c;是国内激光…

如何通过四维轻云SDK开发打造智慧景区管理平台?

智慧景区管理平台通常是基于GIS技术&#xff0c;在三维实景地图的基础上&#xff0c;接入景区各类传感设备、第三方系统数据&#xff0c;进行业务功能的梳理及开发。但对于没有GIS开发经验的团队而言&#xff0c;地图开发具有一定的技术门槛&#xff0c;尤其是需要在前端解决好…

VR全景在智慧园区中的应用

VR全景如今以及广泛的应用于生产制造业、零售、展厅、房产等领域&#xff0c;如今720云VR全景更是在智慧园区的建设中&#xff0c;以其独特的优势&#xff0c;发挥着越来越重要的作用。VR全景作为打造智慧园区的重要角色和呈现方式已经受到了越来越多智慧园区企业的选择和应用。…

记事小本本

记事小本本 实现效果 相关代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</titl…

Zookeeper详解

1.Zookeeper概述 1.Zookeeper概念 Zookeeper是 Apache Hadoop 项目下的一个子项目&#xff0c;是一个树形目录服务 Zookeeper 翻译过来就是动物园管理员&#xff0c;他是用来管 Hadoop&#xff08;大象&#xff09;、Hive(蜜蜂)、Pig(小猪)的管理员。简称zk Hadoop: 存储海…

Java项目:46 ssm005基于SSM框架的购物商城系统+jsp(含文档)

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 项目是单体ssm电商水果平台&#xff0c;包括前台商城平台及后台管理系统 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、…

Buuctf-Web-[极客大挑战 2019]EasySQL 1 题解及思路总结

​ 启动靶机 目录 题要做题过程第一步——找到页面与数据库产生交互的地方第二步——查看SQL语句闭合方式判断SQL注入闭合方式&#xff1a;方法一&#xff1a;使用\(转义字符)来判断SQL注入的闭合方式方法二&#xff1a;输入1、1、1"判断SQL语句闭合方式 第三步——进行SQ…

代理IP如何应对自动化测试和爬虫检测

目录 一、代理IP在自动化测试和爬虫中的作用 二、代理IP的优缺点分析 1.优点 2.缺点 三、应对自动化测试和爬虫检测的策略 1.选择合适的代理IP 2.设置合理的请求频率和间隔 3.模拟人类行为模式 4.结合其他技术手段 四、案例与代码示例 五、总结 在自动化测试和爬虫开…

Alpha突触核蛋白神经退行性疾病介绍

StressMarq——Alpha突触核蛋白&神经退行性疾病 Alpha突触核蛋白科研背景 • Alpha突触核蛋白约 15kDa, 140个氨基酸 • StressMarq/欣博盛生物在E. coli中过表达人源基因然后将蛋白从细胞质基质中纯化出来 • 未折叠的alpha突触核蛋白单体在12% SDS-PAGE上为~15 kDa的条…

CentOS本地部署Tale博客并结合内网穿透实现公网访问本地网站

文章目录 前言1. Tale网站搭建1.1 检查本地环境1.2 部署Tale个人博客系统1.3 启动Tale服务1.4 访问博客地址 2. Linux安装Cpolar内网穿透3. 创建Tale博客公网地址4. 使用公网地址访问Tale 前言 今天给大家带来一款基于 Java 语言的轻量级博客开源项目——Tale&#xff0c;Tale…

2022年吉林省大学生电子设计竞赛(D题)

一. 使用技术 PWM调速&#xff0c;PID&#xff0c;串口通信&#xff0c;陀螺仪测角度&#xff0c;蓝牙 二. 项目描述 大学的第一个比赛&#xff0c;项目采用主控stm32&#xff0c;车体采用一个四路电机驱动来驱动减速电机&#xff0c;小车依靠8路灰度循迹模块&#xff0c;实…

Keepalive+LVS群集部署

引言 Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案&#xff0c;可以解决静态路由出现的单点故障问题。 一、Keepalive概述 keepalive软件起初是专为 LVS 负载均衡软件设计的&#xff0c;用来管理并监控 LVS集群中各个服务节点的状态&#xff0c;后来又加入了可以…

【VS Code插件开发】自定义指令实现 git 命令 (九)

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端舵手 &#x1f64b;‍♂️ 作者简介&#xff1a;前端领域优质作者、阿里云专家博主&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; ✨优质专栏&#xff1a;VS Code插件开发极…

2m高分辨率土地利用分类矢量数据/植被类型分布数据

土地利用数据是在根据影像光谱特征&#xff0c;结合野外实测资料&#xff0c;同时参照有关地理图件&#xff0c;对地物的几何形状&#xff0c;颜色特征、纹理特征和空间分布情况进行分析&#xff0c;建立统一解译标志的基础之上&#xff0c;依据多源卫星遥感信息&#xff0c;结…

<Linux> 线程控制

目录 一、线程资源的分配 &#xff08;一&#xff09;线程私有资源 &#xff08;二&#xff09;线程共享资源 二、原生线程库 三、线程控制接口 &#xff08;一&#xff09;线程创建 - pthread_create() 1. 一个线程 2. 一批线程 &#xff08;二&#xff09;线程等待 …

webpack5零基础入门-2wepack配置项的了解

在使用webpack之前&#xff0c;我们需要对webpack的配置项有一定的认识。 1.五大核心概念 1.entry&#xff08;入口&#xff09; 指示webpack从哪个文件开始打包 2.output (输出) 指示webpack打包完的文件输出到哪里,如何命令等 3.loader(加载器) webpack本身只能处理js…

数字证书在网络安全中的重要性与实际应用

数字证书作为一种“电子身份证”&#xff0c;在当今数字化的商业环境中有着广泛的实际应用。它主要用于身份认证、加密通信、电子签名和安全访问控制等方面&#xff0c;为各行各业提供了安全可靠的数字化解决方案。 网络安全领域 在网络通信中&#xff0c;数字证书被广泛应用…

【脚本玩漆黑】橙华市全自动练级

文章目录 前言项目结构故事后续 前言 选完预三家&#xff0c;作者来到了橙华市。 众所周知啊&#xff0c;打架输了要掏一半的家产&#xff0c;所以宝可梦世界非常的危险。 为了安全考虑&#xff0c;作者打算在这里升个级。 项目结构 1&#xff0c;安装库。 pip install pynp…

Java后端八股------消息中间件篇

自动确认没收到&#xff0c;实现重复消费问题&#xff0c;可以用业务唯一标识来确定业务是否被消费。 TTL也就是超时时间&#xff0c;一般去dead letter的时间为min(消息的ttl,queue的ttl)。 acksall设置是最安全的&#xff0c;但是效率太低了&#xff0c;实际的生…

「CISP题库精讲」CISP题库习题解析精讲20道

前言 本篇主要对CISP教材第九章《计算环境安全》的一些习题进行讲解&#xff0c;包括20道题&#xff0c;这里只是部分习题&#xff0c;针对第九章可能会多写几章的内容&#xff0c;如果我发布的这些习题里面没有你想找的那道题&#xff0c;你也可以直接私信我&#xff0c;我加…