最近才忙完了一个操作系统的作业,让我们用C语言实现一个Shell。总的来说,其实就是让我们 对系统调用有比较深的了解。

首先 介绍一下我的Shell 所实现的功能。
1.运行可执行程序 即输入某个 标志符号 使得其能在我的Shell中运行,并且不退出当前shell。
2.获得某个程序的中某个字符串的个数(其实就是调用了/bin/里面的grep)

3.使用管道,实现两个子进程之间的联系,当然不能连shell自己都退出了。。

4.定向输出到某个个文本文件中。

但是在这之前,我想先谈谈我对fork()这个函数的理解。

首先我们把最开始的程序叫做F,然后 我们开始运行这个 程序,让我们一条条指令运行!当我们的运行到fork的时候,我们OS将整个程序 复制出几乎完全一样的一个程序(子程序)!注意在此之前的命令已经执行完了,所有的数据空间中的数据都将被复制一份,供子程序使用(包括fork()这个函数也会被复制一份。)。注意是复制一份,并不是让子程序共用父程序的数据空间!(再直白一点就是同一个变量,你在子程序里面调用改变了他,但是当你在父程序里面打印出它时,数值仍然是改变前的)。

前面提到的父子程序,难免会让人产生很多疑惑!有人肯定会问:“你不是说我的父程序 和子程序是完全一样的吗?那我fork出一个子程序有什么用呢?反正都是干的同一件事" 或者问:“那我们怎么区分父子进程呢?”。

我逐个解释。

首先第一 前面我提过,fork也会被复制到子进程中,那么可想而之,两个fork会返回两次,注意不是一个fork返回两次(只是看起来像而已),这就是fork神奇的地方。 fork的调用不需要任何参数,

而他的返回值 是一个整数。

当这个整数大于0时,表示着,当前进程为父程序,并且这个返回的整数表示它的子进程的ID(第几个进程)。
当返回值小于0的时候,表示没能成功创建出子进程。

当等于0的时候表示着,我们的当前进程是子进程。

网上大佬都说 把整个过程看成链表即可,返回值指向下一个进程。

所以人们往往就运用这个特性来设计父程序干啥,程序干啥。这样每次判断一下当前是在父程序还是子程序 就可做不同的事情。也就是说两个进程完全可以干不同的事情。

需要注意的是系统调用函数头文件<unistd.h>

可能又有老哥会问“那执行的时候先执行谁啊?”

当然是“同时”执行,由于concurrency 的存在,中文貌似翻译是并发,这就是指CPU能在极高速度的运转下,不停切换处理对象,让我们觉得貌似他俩同时执行,其实每次cpu都只能对某一个进程进行处理。

至于父子进程谁先执行,我觉得应该问 他俩到底谁先执行完。 这个就难判断了,他们需要执行的内容不一样,出结果的时间当然就不一样,最后谁先结束我们不得而知。

所以往往,我们会在父进程中加入wait 或者waitpid函数,来让父进程来等待子进程。这样能做确保我们Shell的功能实现了以后(当前在父进程),不会立即打断子进程的输出。(这个稍后解释)

实际我们操作fork的时候往往需要搭配exec族的系统调用来配合使用,我们往往希望我们的子程序能运行一些自己的内容或者说是另一个程序。
所以接下里 我想稍微花点时间一个个解释这几个函数。

exec函数一共有六个,其中execve为内核级系统调用,其他(execl,execle,execlp,execv,execvp)都是调用execve的库函数

也就是说最终我们调用的其实还是execve而它的库函数,仅仅是传递参数的方法不同。

我先解释execve的参数传递要求

函数定义:

int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

第一个参数: filename 字符串所代表的文件路径。(这个文件路径包括了 此用户的环境变量中的 PATH路径,和当前程序所在目录。即在这两种路径中寻找符合filename的文件,然后执行它)。

第二个参数:是利用指针数组来传递给执行文件。

第三个参数:传递给执行文件的新环境变量数组。

我还想在这多啰嗦一句 这个环境变量的意思。注意这里我指的全是Linux 环境下的环境变量。

所谓环境变量,其实是一个很大的集合,它包含了一个或者多个应用程序所将使用到的信息。

那一个程序所将用到哪些信息呢?

常见的环境变量

PATH:决定了shell将到哪些目录中寻找命令或程序

HOME:当前用户主目录

MAIL:是指当前用户的邮件存放目录。

SHELL:是指当前用户用的是哪种Shell。

HISTSIZE:是指保存历史命令记录的条数

LOGNAME:是指当前用户的登录名。

HOSTNAME:是指主机的名称,许多应用程序如果要用到主机名的话,通常是从这个环境变量中来取得的。

LANG/LANGUGE:是和语言相关的环境变量,使用多种语言的用户可以修改此环境变量

https://blog.csdn.net/l494926429/article/details/52816334 详细的介绍可以看这里。

这里我们用到了 环境变量 中的PATH。

现在我们先看看,如果我们想运行程序 需要在哪些地方寻找目标文件。

用 echo $PATH 可以查看这些路径,这里有很多路径,但是我们主要关注/usr/bin这个路径,接下来我们再看看这里面有啥。

 可以看到都是些exec 文件,换句话说每当我们在终端里使用ls cd 等命令时起时调用的都是这里面程序,注意这是系统关键文件,不可以随便乱动哦!bin目录下都是二进制可执行文件。/bin目录放置的是最基本的一些命令的可执行文件,比如cp、mv、mkdir、chmod、chown等等;/usr下面也有一个bin目录:/usr/bin,它里面的文件也是一些命令的可执行文件;如果是用户自己安装的软件,软件的主程序文件就会在/usr/local/bin这个目录里面(或者是用户自己指定的安装目录,比如/usr/local/apache/bin)。

https://zhidao.baidu.com/question/1707850194618091740.html  转自这里。

花了点时间解释了环境变量,现在让我继续解释一下其他类型的exec族函数。

int execlp(const char * file,const char * arg,....) 这个函数可以百度到其中的解释。

execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,注意参数的个数有设计者决定。你调用的那个程序需要多少参数你就传几个。最后一个参数必须用空指针(NULL)作结束。如果用常数0来表示一个空指针,则必须将它强制转换为一个字符指针,否则它将解释为整形参数,如果一个整形数的长度与char * 的长度不同,那么exec函数就将出错。如果函数调用成功,进程自己的执行代码就会变成加载程序的代码,execlp()后边的代码也就不会执行了. 转自百度百科

int execvp(const char * path,const char * arg,....,char *const envp[]);

这个参数起时没有特别大的不同,只不过我们的第二个参数,从一个个给目标程序传递参数,变成了一次性传递一组字符串指针,也就是一次性把所有参数传完。

举个栗子。(注意这里传递的参数的结尾同样也需要NULL)

char *args[] = {"./hello","hello" ,NULL};

execvp("./hello",args);

其实 其他的exec族函数基本用法都差不多。大家可以自行百度。

这里我就先暂时总结到这,下一章节,我将大面积解释Shell 功能的具体实现。

从0开始自己用C语言写个shell__01_整体的框架以及fork和exec族函数的理解的更多相关文章

  1. linux c语言 fork() 和 exec 函数的简介和用法

    linux c语言 fork() 和 exec 函数的简介和用法   假如我们在编写1个c程序时想调用1个shell脚本或者执行1段 bash shell命令, 应该如何实现呢? 其实在<std ...

  2. 自己用C语言写dsPIC / PIC24 serial bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). HyperBootlo ...

  3. 自己用C语言写单片机PIC18 serial bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). HyperBootlo ...

  4. 自己用C语言写单片机PIC16 serial bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). 为什么自己写bootl ...

  5. PIC12F629帮我用C语言写个程序,控制三个LED亮灭

    http://power.baidu.com/question/240873584599025684.html?entry=browse_difficult PIC12F629帮我用C语言写个程序,控 ...

  6. 用C语言写个程序推算出是星期几?(用泰勒公式实现)

    在日常生活中,我们常常遇到要知道某一天是星期几的问题.有时候,我们还想知道历史上某一天是星期几.比如: “你出生的那一天是星期几啊?” “明年五一是不是星期天?我去找你玩?” 通常,解决这个问题的最简 ...

  7. 一个用 C 语言写的迷你版 2048 游戏,仅仅有 500个字符

    Jay Chan 用 C 语言写的一个迷你版 2048 游戏,仅仅有 487 个字符. 来围观吧 M[16],X=16,W,k;main(){T(system("stty cbreak&qu ...

  8. 用C语言写解释器(一)——我们的目标

    声明 为提高教学质量,我所在的学院正在筹划编写C语言教材.<用C语言写解释器>系列文章经整理后将收入书中"综合实验"一章.因此该系列的文章主要阅读对象定为刚学完C语言的 ...

  9. 在windows下用C语言写socket通讯实例

    原文:在windows下用C语言写socket通讯实例 From:Microsoft Dev Center #undef UNICODE #define WIN32_LEAN_AND_MEAN #in ...

随机推荐

  1. [ 转载 ] Java基础

    1.String a = “123”; String b = “123”; a==b的结果是什么? 这包含了内存,String存储方式等诸多知识点.ans:同样序列的字符串直接量为一个实例,所以其实引 ...

  2. 尤娜博客系统 Una

    站长资讯平台:Una [‘尤娜’] 只是一个项目代号,没有特殊含义.尤娜是站在巨人的肩膀上开发完成的博客系统,旨在为程序员提供一个极简的内容创作管理平台,尤娜100%开放源代码,如果您对她感兴趣,Fo ...

  3. 5、获取Class中的字段

    5.获取Class中的字段 5.1 getField(String name) 只获取共有的字段 返回一个 Field对象,它反映此表示的类或接口的指定公共成员字段 类对象. /** * 获取字节码文 ...

  4. MySQL备忘点(上)

    给自己看的,所以以举例子为主了 检索数据 SELECT 检索单列 SELECT name FROM student 检索多列 SELECT no, name FROM student  检索所有列 S ...

  5. Spring Boot教程(七)通过springboot 去创建和提交一个表单

    创建工程 涉及了 web,加上spring-boot-starter-web和spring-boot-starter-thymeleaf的起步依赖. <dependencies> < ...

  6. TCP首部的TimeStamp时间戳选项 转载

    TCP应该是以太网协议族中被应用最为广泛的协议之中的一个,这里就聊一聊TCP协议中的TimeStamp选项.这个选项是由RFC 1323引入的,该C建议提交于1992年.到今天已经足足有20个年头.只 ...

  7. VS2010,VS2013 Datagridview控件的编辑列功能,弹窗界面被挤扁了

    搜了很久,没找到解决办法,在一个角落看到说要卸载Framework,实践后可以,发出来记一下. 解决办法: 发现自己电脑上多了Framework4.8,可能安装VS2013的时候自动安装的. 卸载了F ...

  8. php-fpm(绕过open_basedir,结合ssrf)

    环境的安装->https://www.cnblogs.com/zaqzzz/p/11870489.html 1.nginx的畸形访问 因为安装的是php7.0,所以需要手动修改一下(版本低的时候 ...

  9. 安装 PostgreSQL 时丢失 libintl-8.dll 解决方案

     发表于 2013 年 11 月 13 日     修订于 2018 年 05 月 05 日 PostgreSQL 比 MySQL 有更多的高级特性,而且微信支付的数据库也是基于 PostgreSQL ...

  10. PHP多进程开发与Redis结合实践

    原文:https://blog.51cto.com/laok8/2107892?source=drh 业务逻辑介绍: 用户在 APP 上发帖子,然后存储到 Redis 的 List 列表中 利用 Li ...