您的位置: 首页 - 站长

discuz建网站拓客团队怎么联系

当前位置: 首页 > news >正文

discuz建网站,拓客团队怎么联系,在什么网站可以做硬件项目,怎样下载门户网站文章目录 1. 系统中的文件2. 回顾C中的文件接口3. 文件类的系统调用3.1 open3.2 文件描述符 4. IO的基本过程5.重定向5.1 引入重定向5.2 系统中的重定向接口 6. 缓冲区问题7. 简单版shell的实现 1. 系统中的文件 在学习完Linux权限后#xff0c;我们清楚的知道#xff1a;文… 文章目录 1. 系统中的文件2. 回顾C中的文件接口3. 文件类的系统调用3.1 open3.2 文件描述符 4. IO的基本过程5.重定向5.1 引入重定向5.2 系统中的重定向接口 6. 缓冲区问题7. 简单版shell的实现 1. 系统中的文件 在学习完Linux权限后我们清楚的知道文件 文件内容 文件属性这和进程就很像。所以我们文件的所有操作无非就是对文件内容或属性进行的。 无论何种语言在访问文件之前我们都必须打开文件例如C语言中的fopen。但是我们访问前为什么要打开它呢 文件在没有被访问的时候它存储在磁盘上。学习完进程后我们清楚的知道访问文件其实是进程在访问。 在下面的代码中当程序跑起来进程执行到fopen时文件才被打开。 进程是在内存中的最终要被CPU执行当进程执行文件操作时但文件在磁盘上根据冯诺依曼体系CPU不能访问磁盘。 所以文件也必须加载到内存中否则进程访问不了因为CPU访问不了所以打开文件的本质是将文件加载到内存中。 因为文件 内容 属性所以加载时加载的就是文件的内容或属性。 我们知道一个进程可以打开多个文件多个进程就可以打开更多的文件。 既然操作系统要管理进程那么操作系统也要管理加载到内存中的文件文件是谁加载的什么时候加载的?要不要释放…操作系统管理加载到内存中文件的方式先描述再组织 因此我们研究打开的文件是在研究进程和文件的关系 文件就可以分为 被打开的文件 - -内存中未被打开的文件 - - 磁盘中

  1. 回顾C中的文件接口 在C语言中我们学习了很多的文件相关接口例如 在系统文件中我们重点关注 w 与 a 执行任何一个程序进程默认会打开三个输入输出流分别是stdin, stdout, stderr。
    仔细观察发现这三个流的类型都是FILE而且fopen的返回值也是FILE。 在任何一个语言中都会提供类似的三个流既然语言都支持那这本身并不属于语言的特性而是属于操作系统所做的工作。 所以各种语言提供的访问文件键盘、显示器的接口本质都是封装的文件类的系统调用接口。
  2. 文件类的系统调用 3.1 open 参数
    第二个参数是标记位上方列出了常用的选项它们本质上都是宏可以组合使用。 但是以前使用多个宏时我们需要传递多个参数为什么这里一个int flag就能解决呢- - - 位图32个比特位可以存储32个选项。如果是这样的话那上方列出的宏选项它们应该只有一个比特位为1。 举个栗子 上方代码就通过检查flag对应位置上是0还是1就可以实现传递多个选项的效果。 下面我们使用一下系统调用open 运行后发现文件不存在时确实创建了但是文件的权限为什么是错乱的呢 因为创建文件和起始权限是操作系统的两个分支功能操作系统不会在系统调用上让你创建文件时就按照默认权限来它也没有这个权力。你要告诉系统调用文件的默认起始权限是什么。 我们需要通过第三个参数mode指定这不就是权限那里的chmod吗 权限不是666是因为有 umask去除umask可在函数中调用 umask(0); 每个进程都有自己的umask继承自系统如果你设置了umask它采用就近原则使用用户设置的不使用系统的了。 有了open我是不是就可以自己实现一个touch命令了。 如果是touch命令则获取命令行中的第二个参数调用open(argv[1]O_WRONLY | O_CREAT0666) 不重新设置umask时文件权限就是664 返回值 打开并新建一个文件后会返回新文件的文件描述符descriptor 上方的代码返回的文件描述符为什么是3呢- - 后面讲 它有什么用呢 使用文件描述符可对文件进行操作
    修改要写入的内容后
    为什么此时写到文件中的内容没有清空之前的内容呢 - - 因为你只告诉open系统调用只写没有就创建没告诉我要清空。 清空/截断的选项O_TRUNC 带上该选项写入前就会清空。 新增式的写需要带上选项O_APPEND 有了上面的知识后我们就可以清楚的知道fopen - openfclose - closefwrite-writefread - readw、a、r就对应相应的宏所以语言层的文件库函数本质上就是封装了文件的系统调用接口。 3.2 文件描述符 open的返回值文件描述符为什么从3开始呢 Linux进程默认情况下会有3个缺省打开的文件描述符分别是标准输入0标准输出1标准错误2。 0,1,2对应的物理设备一般是键盘显示器显示器 那么此时就可以直接使用三个默认打开的文件描述符了 #include stdio.h#include sys/types.h#include sys/stat.h#include fcntl.h#include string.hint main(){char buf[1024];ssize_t s read(0, buf, sizeof(buf));//从标准输入中读if(s 0){buf[s] 0;write(1, buf, strlen(buf));//输入到标准输出中}return 0;}所以这个文件描述符到底是什么呢- - 连续的数字是数组下标吗 下面我们从内核的角度看一下
    内核源代码中确实存在文件描述符表
    所以每次我们在open时当前进程会去自己的文件描述符表中的 结构体指针数组 中从前向后找空位置。 那么在系统层面文件描述符就是访问文件的唯一方式。 但是我们平常在使用C标准库提供的文件访问函数时并没有看到相应的fd呀- - 这是因为库中进行了封装全部封装到FILE中去了 上面所说的我都能理解可是键盘、显示器也能被当作文件来看吗Linux下一切皆文件又如何理解呢 对于硬件来说它们都有一个特点它们统一叫做外设。操作系统是通过类似于链表的方式将各个外设管理起来的。
    但对于很多外设(struct device)来说它们的属性可以相同但是属性的内容可以不同可是键盘就是键盘、显示器就是显示器对这两个外设进行操作的方法只有两个读和写IO所以它们的方法一定不同因为不同的设备访问对应硬件的方式是不同的。那是怎么做到统一以文件的视角来访问的呢 对于每一种设备都要给其定义读写方法。 例如键盘定义了读和写的方法但是只需要实现读方法显示器定义了读和写的方法但是只需要实现写方法。对于未实现的方法就让它为空。在对设备进行管理时只需要在其struct file中设置两个函数指针让其指向对应的方法不关心方法的实现,屏蔽了底层的差异。 从struct file向上开始所有访问硬件的方法统一叫做读和写struct file就相当于做了一次封装。Linux系统对外部只需要提供struct file对象这叫做虚拟文件系统vfs。那这个struct file是怎么让用户看到的呢通过文件描述符让进程看到。 因为所有用户的行为都会被转化为进程站在进程角度它只需要通过fd找到对应的struct file执行struct file中对应的方法即可完成对设备的操作。 所以Linux中一切皆文件是对进程而言的 在一个结构体当中一切皆文件但是底层却有不同硬件这种模式在C 中叫做多态。struct file就是父类因为struct file都一样硬件的struct device就是子类子类实现了不同的读写方法继承就是产生了上下层关系这就是C语言中实现多态的一种形式。 内核源代码
  3. IO的基本过程 写文件
    其实write做的工作就是将要写的内容拷贝到文件的内核缓冲区即可。 读文件 先从磁盘读到文件的内核缓冲区在调用read函数拷贝到指定位置 修改文件 修改的本质也是先读取在写入先将文件加载到文件的内核缓冲区在内核中修改然后再将修改后的内容从缓冲区加载到外设中 存在缓冲区的原因内存的操作快外设的操作慢 5.重定向 5.1 引入重定向 先看一个现象 为什么关掉0以后我自己打开的文件就是0了呢 因为进程打开文件需要给进程分配新的fdfd的分配规则最小的没有被使用的fd会从上往下扫描文件描述符表找未被使用的最小的。 如果我把1号关了此时fd1就应该为1由于printf底层封装了fprintffprintf中有FILE*参数默认fd为1所以只会向1号描述符中打印不管1指向哪里那么就应该将内容打印到log1.txt中 但是log1.txt中怎么什么都没有呢
    fflush(stdout)后为什么又有了呢 - - 跟缓冲区相关后面讲。 为什么本来应该向显示器中打印的内容最终却写到了文件中呢 因为在上层的调用中fprintf、fwrite等向stdout中打印的所有调用它们只认文件描述符1。 1号描述符表并没有变我们只改变了1中的内容这就叫做重定向 所以重定向的原理就是更改文件描述符表中特定下标中的内容。重定向的过程中上层代码毫不知情 5.2 系统中的重定向接口 dup2系统重定向接口 本质就是用新的fd覆盖到指定位置 被覆盖的将被关掉。 输出/追加重定向 那追加重定向不就是只需要将选项 O_TRUNC换成O_APPEND了吗 输入重定向 在写我们自己的shell时思考一下程序替换会影响重定向的结果吗 不会因为程序替换仅仅是替换进程所对应的代码和数据必要时修改mm_struct中页表的映射关系。对于一个进程“上层”的东西task_struct、file_struct、mm_struct等都不会修改依旧使用重定向以后的内容。 6. 缓冲区问题 在上面的内容中我们遗留了一个问题。 那就是为什么我重定向以后它并没有直接给我写到指定文件中而fflush(stdout)后就写进去了呢 首先我们要知道在C语言中我们使用的printf、scanf、fprintf、fscanf、fwrite、fread等都要求有一个FILE*的指针。所以在调用这些函数进行操作时它并没有直接调用系统调用read、write直接拷贝到文件的内核缓冲区因为频繁的调用系统调用的成本太高了效率低。所以怎么能提高效率呢通过用户级缓冲区 你printf、fprintf等只需要将内容拷贝到用户级缓冲区中任务就完成了无非就是在拷贝的过程中进行一下格式化等用户级缓冲区攒了足够多的数量在统一调用系统调用写入到文件的内核缓冲区提高了效率。该缓冲区在FILE结构体中刷新的本质就是从用户级缓冲区拷贝到内核的文件缓冲区。 用户级缓冲区有以下几种刷新方案 显示器文件行刷新普通文件缓冲区写满再刷新不缓冲语言级无需刷新 我们将最开始的代码修改一下会发现如果我不调用任何的close或者调用fclose内容可以正常打印出来这是为什么呢 因为当一个进程退出的时候会自动刷新自己的缓冲区所有的FILE对象内部包括stdin、stdout、stderrfclose是C语言级的调用它关闭FILE时也会自动刷新。 那close(fd)后为什么不会刷新呢 此时尽管“表面上”是向显示器中打应该是行刷新那么我不自己刷新应该也可以显示出来呀 - - 此时不是行刷新因为显示器文件早就关闭了1中放的是普通文件应执行写满刷新的策略。 那操作系统是什么时候将文件内核缓冲区的内容刷新到外设中的呢我能不能控制呢通过系统调用fsync 一个简单的题目 如果在调用函数时不加 \n即使不重定向也是上图所示的打印效果。
  4. 简单版shell的实现 #includecstdio #includestdlib.h #includestring #includestring.h #includeunistd.h #includesys/wait.h #includesys/types.h #includesys/stat.h #includefcntl.h using namespace std;const int basesize 1024; const int argvnum 64; const int envnum 64; //存储命令行参数的两个全局变量 char* g_argv[argvnum]; int g_argc; //存shell自己的环境变量 char* g_env[envnum];//如果这两个设置为局部变量则会写入一个空白 //局部变量销毁环境变量表中存的是这两个变量的地址所以就是空 char pwd[basesize]; char pwdenv[basesize];//存储之前的退出码 int lastcode 0;//重定向相关全局变量 #define NonRedir 0 #define InputRedir 1 #define OutputRedir 2 #define AppendRedir 3 int redir NonRedir; char* filename nullptr; //去除空格 #define TrimSpace(pos)\do {\while(isspace(pos)){\pos;}}while(0)
    string GetName() {string username getenv(USER);return username.empty() ? None : username; }string GetHostName() {string hostname getenv(HOSTNAME);return hostname.empty() ? None: hostname; }bool repalcePwd() {for(int i 0; g_env[i]; i){if(strncmp(g_env[i],pwdenv,3) 0){g_env[i] pwdenv;return true;}}return false; }string GetPwd() {if(getcwd(pwd,sizeof(pwd)) nullptr)return Node;//将当前的工作路径保存至环境变量中snprintf(pwdenv,sizeof(pwdenv),PWD%s,pwd);//putenv(pwdenv); //将新的pwd,添加到系统的环境变量表中repalcePwd();//将新的pwd,添加到自己的环境变量表中return pwd;// string pwd getenv(PWD); }string LastDir() {string pwd GetPwd();if(pwd / || pwd None)return pwd;//寻找最后一个文件夹int pos pwd.rfind(/);return pwd.substr(pos1); }//1.显示命令行提示符 void ShowCommandLine() {char command_line[basesize];snprintf(command_line,basesize,[%s%s %s]#,\GetName().c_str(),GetHostName().c_str(),LastDir().c_str());printf(%s,command_line); fflush(stdout); }//2.读取命令行参数 bool GetCommandLine(char command_buffer[],int size) {//读取一行的用户输入char
    ret fgets(command_buffer,size,stdin);if(ret NULL){return false; //获取输入失败}//处理回车键command_buffer[strlen(command_buffer) - 1] \0;if(strlen(command_buffer) 0)return false;return true; }void debug() {printf(argc:%d\n,g_argc);for(int i0; g_argv[i]; i){printf([%d] %s\n,i,g_argv[i]);} }void CheckRedir(char command_line[],int len) {int end len - 1;while(end 0){if(command_line[end] ){redir InputRedir;command_line[end] \0;filename command_line[end1];//过滤空格TrimSpace(filename);break;}else if(command_line[end] ){if(command_line[end - 1] ){redir AppendRedir;command_line[end] \0;command_line[end-1] \0;filename command_line[end1];//过滤空格TrimSpace(filename);break;}else {redir OutputRedir;command_line[end] \0;filename command_line[end1];//过滤空格TrimSpace(filename);break;}}else {end–;}} }void InitCommand() {//命令行清空memset(g_argv,0,sizeof(g_argv));g_argc 0;//每次检查是否有重定向前先清空redir NonRedir;filename nullptr; }void AnalyCommand(char command_line[]) {const char sep[10] ;//指定分隔符g_argv[g_argc] strtok(command_line,sep);//先提取第一个//strtok读取失败返回nullwhile((g_argv[g_argc] strtok(nullptr,sep)));//依次提取后面的g_argc–;//个数要-1 }void AnalyzeCommandLine(char command_line[]) {InitCommand();//printf(redir before:%s\n,command_line);//检查重定向CheckRedir(command_line,strlen(command_line));// printf(redir:%d\n,redir);// printf(filename:%s\n,filename);// printf(redir after:%s\n,command_line);AnalyCommand(command_line); }void Redir() {//程序替换不会影响重定向因为内核数据结构中的file_struct没变//程序替换替换的是代码和数据。int fd -1;if(redir InputRedir){if(filename){fd open(filename,O_RDONLY);if(fd 0){exit(3);}dup2(fd,0);}else {exit(2);}}else if(redir OutputRedir){if(filename){fd open(filename,O_CREAT | O_WRONLY | O_TRUNC,0666);if(fd 0){exit(5);}dup2(fd,1);}else {exit(4);}}else if(redir AppendRedir){if(filename){fd open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);if(fd 0){exit(7);}dup2(fd,1);}else {exit(6);}}else {//没有重定向//do nothing} } bool ExecCommandLine() {//shell创建子进程执行任务pid_t id fork();if(id 0)return false;if(id 0){//child执行命令//重定向应由子进程做Redir();execvpe(g_argv[0],g_argv,g_env);exit(1);//执行失败退出码为1}int status 0;pid_t rid waitpid(id,status,0); //阻塞式等待if(rid 0){//等待成功//1.子进程正常结束if(WIFEXITED(status)){lastcode WEXITSTATUS(status);}else {lastcode 99;}return true;}return false; }void AddEnv(const char* str) {int index 0;while(g_env[index]){index;}g_envindexmalloc(strlen(str) 1);strncpy(g_env[index],str,strlen(str)1);g_env[index] nullptr; }bool CheckAndExecBuildCommand() //判断是否式内建命令 {//枚举几个内建命令if(strcmp(g_argv[0],cd) 0){if(g_argc 2){chdir(g_argv[1]);lastcode 0;}else{lastcode 1;}return true;}else if(strcmp(g_argv[0],export) 0){if(g_argc 2){AddEnv(g_argv[1]);lastcode 0;}else{lastcode 2;}return true;}else if(strcmp(g_argv[0], env) 0){for(int i 0; g_env[i]; i){printf(%s\n,g_env[i]);}lastcode 0;return true;}else if(strcmp(g_argv[0],echo) 0){if(g_argc 2){if(g_argv[1][0] $){if(g_argv[1][1] ?){printf(lastcdoe:%d\n,lastcode);lastcode 0;}}else{printf(%s\n,g_argv[1]);lastcode 0;}}else{lastcode 3;}return true;}return false; }void InitEnv() {extern char** environ;//从系统shell中获取环境变量int index 0;while(environ[index]){int len strlen(environ[index]);g_envindexmalloc(len 1);strncpy(g_env[index],environ[index],len 1);index;}g_env[index] nullptr; }int main() {InitEnv();char command_buffer[basesize];while(true){ //1.显示命令行提示符ShowCommandLine();//slsleep(2);//printf(\n);//2.读取命令行参数if( !GetCommandLine(command_buffer,basesize) ){continue; }//3.解析命令行参数AnalyzeCommandLine(command_buffer);//debug();if(CheckAndExecBuildCommand()) //判断是否式内建命令{continue;}//4.执行命令ExecCommandLine();}return 0; }