操作系统系列:关于终端Shell

操作系统系列:关于终端

  • Shell
  • 在Win32上创建一个新进程
  • 重定向输入和输出


Shell

Unix命令处理器或者Shell都是进程,它获取用户键入的命令,fork出一个进程,子进程调用exec来执行用户的命令,父进程等待子进程执行结束。
这是一段简单的shell伪代码:

   pid_t pid;while (1) {GetNextCommand();pid = fork();if (pid == 0) {ExecCommand();PrintErrorMsg()exit(0);}else if (pid > 0) wait();else HandleForkFailure();}

Unix 允许用户通过在命令行中添加与号 (&) 来实现在后台运行命令,当进程在后台运行时,他不能从终端接收用户输入,并立即显示 shell 提示符,允许用户在后台进程完成之前输入新命令。对于上面的伪代码,如果想要在后台运行,开发者要做些什么呢?

在Win32上创建一个新进程

这是在Win32上创建新进程的API:

BOOL CreateProcess( LPCTSTR lpApplicationName, /* executable program */LPTSTR lpCommandLine,      /* command line args  */LPSECURITY_ATTRIBUTES lpProcessAttributes,  /* use NULL */LPSECURITY_ATTRIBUTES lpThreadAttributes,   /* use NULL */BOOL bInheritHandles,  /* does proc inherit parents open handles */DWORD dwCreationFlags, LPVOID lpEnvironment, /* if NULL, use parent environment */LPCTSTR lpCurrentDirectory, /* if NULL, use parent curr dir */LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation 
); typedef struct PROCESS_INFORMATION {HANDLE hProcess;HANDLE hThread;DWORD  dwProcessId;DWORD  dwThreadId;
} PROCESS_INFORMATION;

CreateProcess相当于Unix上的fork()和exec()。相比于Unix的系统调用,Win32 APIs几乎都会有很多的参数。

  • LPCTSTR lpApplicationName 这是要执行的进程的路径名,相当于 Unix execl 命令的第一个参数,它可以是相对路径名或绝对路径名。
  • LPTSTR lpCommandLine 这个参数通常是 NULL
  • LPSECURITY_ATTRIBUTES lpProcessAttributes 进程属性,通常是NULL
  • LPSECURITY_ATTRIBUTES lpThreadAttributes 线程属性,通常是NULL
  • BOOL bInheritHandles 如果该值为 TRUE,则每表示个打开的文件句柄(或其他句柄)也可以在子进程中打开
  • DWORD dwCreationFlags 通过该值设置一些标志,默认值是0
  • LPVOID lpEnvironment 允许开发者改变环境,如果该值取NULL表示子进程继承父进程的环境
  • LPCTSTR lpCurrentDirectory 允许开发者改变目录,如果为 NULL,则子进程与父进程有相同的当前工作目录
  • LPSTARTUPINFO lpStartupInfo 这是一个指向有关如何呈现新进程的信息的指针。 在Windows环境中,一个新进程通常是一个新窗口,它会告诉操作系统该窗口放置在哪里,窗口的高度和宽度以及其他信息。
  • LPPROCESS_INFORMATION lpProcessInformation 这是一个向父级返回有关子级的信息的结构,例如进程 ID 和句柄。
    与大多数 Win32 API 一样,此调用在成功时返回 TRUE,在失败时返回 FALSE。

下面是一段简短的示例代码,用于启动记事本。

#include <windows.h>
#include <stdio.h>
#include <string.h>char *GetErrorMessage() 
{char *ErrMsg;FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |              FORMAT_MESSAGE_IGNORE_INSERTS,NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language(LPTSTR) &ErrMsg,0,NULL );return ErrMsg;
}int main()
{char commandline[255];PROCESS_INFORMATION ProcessInfo;STARTUPINFO StartupInfo;strcpy(commandline,"notepad");GetStartupInfo(&StartupInfo);if (CreateProcess ("c:\\winnt\\notepad.exe",commandline, NULL, NULL,FALSE, 0, NULL, NULL, &StartupInfo,&ProcessInfo) == TRUE) {printf("Create was successful\n");printf("Proc id is %d\n",ProcessInfo.dwProcessId);}else {printf("error, CreateProcess failed, error number %d\n",GetLastError());printf("%s\n",GetErrorMessage());}return 0;
}

请特别注意 CreateProcess 的第一个参数,由于反斜杠是字符串中的转义字符,因此字符串中的反斜杠字符必须用双反斜杠表示。
这在 Unix 中不会出现太多,但在 Windows 中却是个一直存在的问题,因为反斜杠是目录分隔符。

重定向输入和输出

不管在 Unix上,还是在Windows 上,当创建一个新进程时,都会自动创建三个 I/O 流,它们称之为标准输入、标准输出和标准错误。

  • 标准输入默认为控制终端的键盘;
  • 标准输出和标准错误默认为控制终端的监视器。

监视器默认有两个单独的输出流的原因是,有时用户希望将正常输出重定向到文件,但希望将错误消息显示在终端上(或发送到 不同的文件)。

  1. 用户可以在 shell 提示符下使用 > 运算符将进程的输出从终端重定向到文件。 例如:

    ls -l > outfile
    

    这个操作会将 ls -l 命令的输出发送到名为 outfile 的文件,而不是终端。

    shell 还可以将标准输入从键盘重定向到带有 < 字符的文件。

    每个 Unix 进程都有一个与其关联的文件描述符数组。系统调用 open 就返回一个文件描述符,文件描述符是一个低位正整数。

    按照约定,标准输入是文件描述符 0,标准输出是文件描述符 1,标准错误是文件描述符 2。

    当程序打开文件时,通常会为其分配最低的未使用文件描述符。写入到终端的函数(例如 printf)或者从键盘读取的函数(例如 scanf、getchar)都会调用写入和读取系统调用,并将文件描述符设置为标准输出和标准输入。

    如果开发者想要将标准输入或标准输出从程序内重定向到文件,请使用 dup 或 dup2 系统调用。 这两个调用会复制一个文件描述符,对 dup2 的调用需要两个参数:fd1 和 fd2,它们都是文件描述符。 第一个参数 fd1 必须是已打开的文件描述符, 该调用使 fd2 引用与 fd1 相同的文件。 如果fd2打开,则首先要把它关闭。

    请看下面的例子:

    /* dup2.c - a demo of the dup2 call */
    #include <stdio.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <unistd.h>
    int main()
    {int fd, retval;fd = open("temp",O_WRONLY | O_CREAT | O_TRUNC, 0200 | 0400); /* fd is probably 3, the lowest unused file descriptor */if (fd < 0) {perror("Error on opening temp");exit(0);}printf("This line written on the terminal\n");retval = dup2(fd,1); if (retval != 1) {perror("Error on dup2");exit(0);}   printf("This line is written to the file temp\n");return 0;
    }
    

    该程序打开一个名为 temp 的文件进行写入,然后它调用 printf,将一行写入终端,下一行是程序的关键语句:

    retval = dup2(fd, 1);
    

    该行使文件描述符 1 引用 fd 引用的文件,由于文件描述符 1 引用标准输出,因此首先要将其关闭。

    在 printf 函数中,有一行代码如下所示:

    n = write(1,.....
    
  2. 通常,文件描述符1指的是标准输出,但我们的程序重新定义了它,使其指的是文件temp,因此,对 write 的调用将写入文件而不是终端。如果成功,dup2 返回其第二个参数的值,否则返回负值。

    dup 是 dup2 的老版本,它只使用一个参数,即要复制的文件描述符,并将最低的未使用文件描述符设置为等效。 要使用此功能,请在调用 dup 之前根据需要关闭文件描述符 0 或 1。

    请参见dup2的帮助文档。

    • NAME
      dup2 - 复制打开的文件描述符
    • SYNOPSIS
      #include <unistd.h>
      int dup2(int fildes, int fildes2);
    • DESCRIPTION
      dup2() 函数使文件描述符 fildes2 引用与 fildes 相同的文件。 fildes 参数是引用打开文件的文件描述符,fildes2 是一个非负整数,小于调用进程允许的打开文件描述符最大数量的当前值。 请参阅 getrlimit(2)。 如果 fildes2 已经引用了一个打开的文件,而不是 fildes,则首先将其关闭。 如果fildes2引用fildes,或者fildes不是有效的打开文件描述符,则fildes2不会首先被关闭。dup2() 函数相当于 fcntl(fildes, F_DUP2FD, fildes2).
    • RETURN VALUES
      成功完成后,将返回表示文件描述符的非负整数。 否则,返回-1并设置errno以指示错误。
    • ERRORS
    • EBADF - fildes参数不是有效的打开文件描述符。.
    • EBADF - files2参数为负数或不小于 getrlimit(RLIMIT_NOFILE, …) 返回的当前资源限制。
    • EINTR - 在调用dup2时返回了一个信号。
    • EMFILE - 进程打开了过多的文件。
  3. 调用 fork 后会创建一个与旧进程相同的新进程,父进程的文件描述符表也会复制到子进程,因此如果父进程有一个打开的文件,子进程将继承这个打开的文件。 此外,如果父进程复制了文件描述符,子进程也将继承它,文件描述符表也会通过对系统调用 exec 系列成员的调用进行复制。

    这对于 shell 的实现很重要,shell 分叉出一个新进程来执行命令,如果用户指定输出到文件而不是标准输出,那么shell 可以使用 dup2 来将标准输出重定向到文件,然后再调用 exec 来执行命令。

祭出一个编程小作业

请编写一个简单的 shell,它在一个无限循环中重复执行以下操作:

  • 显示提示 (>)
  • 从用户处获取命令行
  • 解析命令行
  • 执行命令
  1. 用户输入的命令行应包含一个命令,该命令可以是相对路径名或绝对路径名,后跟零个或多个参数,然后是输入和输出重定向。 stdin 或 stdout 都可以重定向,或者两者都不能重定向。
    如果用户附加一个与号 (&),则该进程应在后台运行。

    以下都是有效的命令行:

    a.out
    a.out arg1 arg2
    a.out arg1 arg2 > outfile < infile
    a.out arg1 arg2>outfile &

    这个作业最难的部分是解析命令行,您可以下载一个文件 ParseCommandLine.h,它定义了一个 CommandData 结构体,包含执行命令所需的命令行中的所有数据。

    struct CommandData {char *command;  /* the pathname of the command */char *args[11]; /* arguments to the command */int numargs;    /* the number of arguments */char *infile;   /* the file for input redirection, NULL if none */char *outfile;  /* the file for output redirection, NULL if none */int  background;  /* 0 if process is to run in foreground, 1 if in background */
    };
    
  2. 它还定义了一个函数:

    int ParseCommandLine(char *line, struct CommandData *data)
    

    它接受两个参数,即用户输入的命令行,以及指向 CommandData 结构的指针(其内存必须由调用函数分配)。 该函数解析命令行并填充 CommandData 结构:

    • 如果没有错误,则返回 1;
    • 如果用户输入无效命令,则返回 0。

    在后一种情况下,数据的内容是不确定的。

  3. 某些对于真实 shell 具有特殊含义的字符不能与此 shell 一起使用。

  • 命令名和路径名只能使用以下字符:大写或小写字母、数字、下划线、斜杠、连字符或点。
  • <、> 和 & 字符具有特殊含义,不能在文件名中使用。
  • 任何其他字符(例如美元符号、星号、问号、引号、逗号、管道、双引号或波形符)都将在 ParseCommandLine 函数中生成错误条件。
  • 文件名和路径名的名称中不能有空格。
  1. 如果命令是 quit 你的程序应该终止。

  2. 您的 shell 应该执行该命令,根据需要重定向 stdin 和 stdout,并将参数传递给程序。 请注意,按照约定(因此,对于此分配),argv[0] 是实际命令,您必须填写此命令。
    如果命令行是:

    a.out arg1 arg2 <infile> outfile
    

    当a.out启动时,argv[0]的值应该是a.out,argv[1]的值应该是arg1,argv[2]的值应该是arg2。

  3. 您的 shell 不需要搜索路径,所有相对路径名都应从当前工作目录开始。 请注意,这意味着如果命令行是 ls,您的程序将回复 command not find(除非当前目录中碰巧存在名为 ls 的可执行文件),要运行 ls,用户必须输入 /bin/ls (并注意您的 shell 必须将 argv[0] 设置为 ls)。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

常用的金融小知识的简单理解

m1和m2 剪刀差扩大 说明经济向好&#xff08;牛市&#xff09;&#xff0c;否则m1变为m2 剪刀差收窄&#xff0c;经济回落 社融-社会融资规模 社融数据增长意味着人们看好未来经济,敢于贷款,赚钱效应增加 cpi-居民消费价格指数 cpi上升意味着通过膨胀 ppi-生产者价格指数…

Android Canvas状态save与restore,Kotlin

Android Canvas状态save与restore&#xff0c;Kotlin private fun f1() {val bitmap BitmapFactory.decodeResource(resources, R.mipmap.pic).copy(Bitmap.Config.ARGB_8888, true)val canvas Canvas(bitmap)val paint Paint(Paint.ANTI_ALIAS_FLAG)paint.color Color.RED…

初始redis

目录 前言&#xff1a; 1.盛赞redis 2 redis特性 3.redis的典型应用场景 4.安装并启动redis 前言&#xff1a; 本章将带领读者进入Redis的世界&#xff0c;了解它的前世今生、众多特性、典型应用场景、安装配置、如何用好等&#xff0c;最后会对Redis发展过程中的重要版本…

EMNLP 2023 亮点回顾:大模型时代下的 NLP 研究

作为自然语言处理&#xff08;NLP&#xff09;领域的顶级盛会&#xff0c;EMNLP 每年都成为全球研究者的关注焦点。2023 年的会议在新加坡举行&#xff0c;聚集了数千名来自世界各地的专家学者&#xff0c;也是自疫情解禁以来&#xff0c;中国学者参会最多的一次。巧的是&#…

English phrase

more challenging yet more practical 更有挑战性但是更符合实际的pairwise 成对的fine-grained 精细的manifold 流形zero shot learning 0样本学习narrow the gap 缩小差距w.r.t with respect to 考虑到be different from in 跟某些人在某些方面不同&#xff0c;例如&#xff…

appium工具相关

一、appium基本介绍 1、appium 基本介绍 定义&#xff1a;appium 就是一款非常流行和好用的第三方工具&#xff0c;通过该工具我们可以配合 python 脚本实现 IOS / Android 多平台的APP 自动化测试。作用&#xff1a;在编写测试脚本的PC机和运行 APP 的真机或设备之前充当一个…

CSS操纵元素的禁用和启用

通常表单控件都会有属性readonly、disabled对元素进行只读、禁用等操作。 而有时候我们想要div也达到类似效果&#xff0c;可以用CSS样式pointer-events: none进行控制。 科普知识 CSS样式的pointer-events: none用于控制一个元素能否响应鼠标操作。当该属性设置为none时&am…

DC-8靶场

目录 DC-8靶场链接&#xff1a; 首先进行主机发现&#xff1a; sqlmap得到账号密码&#xff1a; 反弹shell&#xff1a; exim4提权&#xff1a; Flag&#xff1a; DC-8靶场链接&#xff1a; https://www.five86.com/downloads/DC-8.zip 下载后解压会有一个DC-8.ova文件…

js中BOM对象

BOM操作&#xff1a; 概念&#xff1a;BOM操作的时候&#xff0c;操作的整个浏览器&#xff0c;浏览器被封装成一个对象&#xff0c;这个对象就是window。 window对象就是顶级对象。 window对象的特点为&#xff1a;凡是window对象中的属性或方法&#xff0c;window顶级对象…

bash数组的用法

一、单纯数组 #!/usr/bin/env bash#1、定义一个空数组 my_array()#2、增。使用 运算符将元素添加到数组的末尾 my_array("value1") my_array("value2") my_array("value3")#3、增。还可以使用一行的方式一次性添加多个元素到数组 my_array(&qu…

程序员的20大Git面试问题及答案

文章目录 1.什么是Git&#xff1f;2.Git 工作流程3.在 Git 中提交的命令是什么&#xff1f;4.什么是 Git 中的“裸存储库”&#xff1f;5.Git 是用什么语言编写的&#xff1f;6.在Git中&#xff0c;你如何还原已经 push 并公开的提交&#xff1f;7.git pull 和 git fetch 有什么…

C语言 字符串处理相关函数大汇总之(16~20)

16&#xff0c;strlwr 函数&#xff0c;将字符串中的大写字母转换为小写字母。 它位于 <string.h> 头文件中。 函数原型如下&#xff1a; char *strlwr(char *str); 参数&#xff1a; str&#xff1a;要转换的字符串。 返回值&#xff1a; 返回指向转换后的字符串的…

世微AP8105 低功耗PFM DC-DC变换器 升压芯片多种分装

概述 AP8105系列产品是一种效率、低纹波、工作频率高的PFM升压DC-DC变 换器。AP8105系列产品仅需要四个元器件&#xff0c;就可完成将低输入的电池电压变换升压到所需的工作电压&#xff0c;非常适合于便携式1&#xff5e;4节普通电池应用的场合。 电路采用了高性能、低功耗…

Oracle中的dblink简介

Oracle中的dblink简介 是一种用于在不同数据库之间进行通信和数据传输的工具。它允许用户在一个数据库中访问另一个数据库中的对象&#xff0c;而无需在本地数据库中创建这些对象。 使用dblink&#xff0c;用户可以在一个数据库中执行SQL语句&#xff0c;然后访问另一个数据库中…

猫粮什么品牌好?业内人生分享五个口碑好质量好主食冻干猫粮牌子

随着养猫的人越来越多&#xff0c;铲屎官们对猫咪的饮食也越来越注重。除了猫粮&#xff0c;很多铲屎官还会给猫咪准备小零食。那么&#xff0c;猫咪是不是除了猫粮就没有其他可吃的了呢&#xff1f;答案当然不是。猫咪还有猫冻干、冻干猫粮、猫条等可以选择。每个铲屎官都希望…

Java发起SOAP请求代码参考

目录 Java发起SOAP请求代码参考 代码1.组装参数2.加密参数3.发起连接4.解析返回数据 参考 文章所属专区 超链接 代码 1.组装参数 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans&qu…

petalinux2021.1 手动打包BOOT.BIN

在我们单独调试u-boot或者fsbl或者R5程序时只需要编译生成elf后打包生成BOOT.bin&#xff0c;那么打包生成BOOT.bin的方式除了petalinux-package还可以参照Vitis流程使用bootgen工具&#xff0c;该工具在source了Vitis环境变量后就可以使用了。 使用bootgen主要就是要bif格式的…

补题与周总结:leetcode第 376 场周赛

文章目录 复盘与一周总结2967. 使数组成为等数数组的最小代价&#xff08;中位数贪心 回文数判断&#xff09;2968. 执行操作使频率分数最大&#xff08;中位数贪心 前缀和 滑窗&#xff09; 复盘与一周总结 wa穿了第3题&#xff0c;赛时其实想到了思路&#xff1a;中位数贪心…

软考论文评分标准,究竟是什么?

软考论文的评分标准 软考论文具体评分时&#xff0c;参照每一试题相应的“解答要点”中提出的要求&#xff0c;对照下述5个方面评分&#xff1a; 1.贴合题意&#xff08;占比30%&#xff09; 不论是技术、理论或是实践论文&#xff0c;都需要紧扣主题展开深入探讨。 2.应用…

【Linux】如何在 Linux 终端中复制粘贴

Linux 终端中复制粘贴 一、在终端中使用 键盘快捷键 进行复制粘贴 CtrlShiftC/V二、在终端中复制和粘贴的另一种方法是使用&#xff1a;右键单击上下文菜单 。三、在Linux终端 使用鼠标中键&#xff08;滚轮&#xff09;复制粘贴四、为什么 Linux 终端不使用“通用”的 CtrlC 和…