最近才忙完了一个操作系统的作业,让我们用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. Codeforces Round #469 (Div. 1) 949C C. Data Center Maintenance (Div. 2 950E)

    题 OvO http://codeforces.com/contest/949/problem/C codeforces 949C 950E 解 建图,记原图为 G1,缩点,记缩完点后的新图为G2 缩 ...

  2. Centos7下Nexus3的安装和配置

    参考文档:https://help.sonatype.com/repomanager3 1.要使用nexus服务需要安装jdk和maven 1.1.jdk下载地址:https://www.oracle ...

  3. MySQL index使用率查询

    SELECT  t.table_schema AS db,  t.table_name   AS tab_name,  s.index_name   AS index_name,  s.column_ ...

  4. @Component,@Service,@Controller,@Repository

    1.@controller 控制器(注入服务) 2.@service 服务(注入dao) 3.@repository dao(实现dao访问) 4.@component (把普通pojo实例化到spr ...

  5. Java面向对象6(AA ~ AE)

    AE  简单的复数运算(类和对象) (SDUT 4303) import java.util.*; class Complex { int a, b; Complex() { } Complex(in ...

  6. (转)Git操作

    本地修改了许多文件,其中有些是新增的,因为开发需要这些都不要了,想要丢弃掉,可以使用如下命令: git checkout . #本地所有修改的.没有的提交的,都返回到原来的状态 git stash # ...

  7. Spring Cloud Config(三):基于JDBC搭建配置中心

    1.简介 本文主要内容是基于jdbc搭建配置中心,使应用从配置中心读取配置信息并成功注册到注册中心,关于配置信息表结构仅供参考,大家可以根据具体需要进行扩展. 2.Config Server 搭建 2 ...

  8. NTP 协议介绍

    NTP协议 NTP(Network Time Protocol,网络时间协议)是由RFC 1305定义的时间同步协议,用来在分布式时间服务器和客户端之间进行时间同步.NTP基于UDP报文进行传输,使用 ...

  9. Linux设备驱动程序 之 内存池

    内核中有些地方的内存分配是不允许失败的,为了确保这种情况下的成功分配,内核开发者建立了一种称为内存池的抽象:内存池其实就是某种形式的后备高速缓存,它试图始终保存空闲的内存,以便在紧急状态下使用: me ...

  10. Jenkins与gitlib实现自动化部署与持续构建

    Jenkins概念 Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目,无论用的是什么平台.这是一个免费的源代码,可以处理任何类型的构建或持续集成.集成Jenkins可以用于一些测试和 ...