20165324_mybash

实验要求

  • 实验要求:

    1. 使用fork,exec,wait实现mybash
    2. 写出伪代码,产品代码和测试代码
    3. 发表知识理解,实现过程和问题解决的博客(包含代码托管链接)

背景知识

Shell的介绍

  • shell是一个程序,可以称之为壳程序,用于用户与操作系统进行交互。用来区别与核,相当于是一个命令解析器,Shell有很多中,这里列出其中几种

    1. Bourne SHell(sh)
    2. Bourne Again SHell(bash)
    3. C SHell(csh)
    4. KornSHell(ksh)
    5. zsh
  • 各个shell的功能都差不太多,在某些语法的下达下面有些区别,Linux预设就是bash。这里主要介绍bash

bash

  • bash命令是sh命令的超集大多数sh脚本都可以在bash下运行,bash主要有如下这些功能:

    1. 记录历史命令:bash可以记录曾经的命令,保持在~/.bash_history文件中,只保存上次注销登录之后的命令。
    2. tab键自动补全:使用tab见可以自动不全命令或者目录。
    3. alias命令别名:可以使用alias ll='ls -al'来设置命令的别名。
    4. 工作控制:可以将某些任务放在后台去运行,这里不多种介绍。
    5. 程序脚本:可以执行shell脚本文件。
    6. 通配符:在查找相关文件或者执行相关命令时,可以使用通配符*。
    7. 内建命令type:可以使用type命令来查看某个命令是否为内建在bash当中的命令。

进程

  • 进程的状态:

    1. 创建(created)
    2. 执行(running)
    3. 就绪(ready)
    4. 阻塞(blocked)
    5. 终止(terminated)

bash示例和书写流程

  • 命令行输入:gedit hello_shell.sh
  • 编辑器内输入:
#!/bin/bash

for ((i=0; i<10; i++));do
echo "hello shell"
done exit 0
  • 为文件添加可执行权限:$ chmod 755 hello_shell.sh
  • 执行脚本:$ ./hello_shell.sh
  • 运行截图:

  • 说明:

    1. #!/bin/bash:它是bash文件声明语句,表示是以/bin/bash程序执行该文件。它必须写在文件的第一行。
    2. echo "hello bash" : 表示在终端输出“hello bash”。
    3. exit 0 : 表示返回0。在bash中,0表示执行成功,其他表示失败。
  • 执行bash脚本$ ./bash,在终端输出“hello shell”。

fork

  • fork:

    1. 指一个进程,包括代码、数据和分配给进程的资。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
    2. 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

  • 测试代码Testfork.c
#include <unistd.h>
#include <stdio.h>
int main ()
{
pid_t fpid; //fpid表示fork函数返回的值
int count=0;
fpid=fork();
if (fpid < 0)
printf("error in fork!");
else if (fpid == 0) {
printf("i am the child process, my process id is %d\n",getpid());
printf("I'm the child\n");
count++;
}
else {
printf("i am the parent process, my process id is %d\n",getpid());
printf("I'm the father\n");
count++;
}
printf("统计结果是: %d\n",count);
return 0;
}
  • 运行结果

  • 代码分析: 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。 执行完fork后,进程1的变量为count=0,fpid!=0(父进程)。进程2的变量为count=0,fpid=0(子进程),这两个进程的变量都是独立的,存在不同的地址中,不是共用的,这点要注意。可以说,我们就是通过fpid来识别和操作父子进程的。

wait

  • wait:由于fork产生的父子进程的执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略,所以要实现mybash必须使用wait函数。如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.

  • wait的函数原型是:
  #include <sys/types.h> /* 提供类型pid_t的定义 */
#include <sys/wait.h>
pid_t wait(int *status);
  • 返回值: 如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。

    1. 进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经 退出,如果让它找到了这样一个已经变成僵尸的子进程, wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止
    2. 参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:pid = wait(NULL);
    3. 如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。

下面运行一个例子来理解wait调用:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h> int main()
{
pid_t pc, pr;
pc = fork();
if ( pc < 0 ) /* 如果出错 */
{
printf("create child prcocess error: %s/n", strerror(errno));
exit(1);
}
else if ( pc == 0) /* 如果是子进程 */
{
printf("I am child process with pid %d \n", getpid());
sleep(3);/* 睡眠3秒钟 */
exit(0);
}
else /* 如果是父进程 */
{
printf("Now in parent process, pid = %d/n", getpid());
printf("I am waiting child process to exit.\n");
pr = wait(NULL); /* 在这里等待子进程结束 */
if ( pr > 0 ) /*子进程正常返回*/
printf("I catched a child process with pid of %d\n", pr);
else /*出错*/
printf("error: %s/n.\n", strerror(errno));
}
exit(0);
}
  • 运行结果:

  • 代码分析:设定的让子进程睡眠3s的时间,只有子进程从睡眠中苏醒过来,它才能正常退出,也就才能被父进程捕捉到。这里不管设定子进程睡眠的时间有多长,父进程都会一直等待下去。

exec

  • exec:exec命令用于调用并执行指令的命令。exec命令通常用在shell脚本程序中,可以调用其他的命令。如果在当前终端中使用命令,则当指定的命令执行完毕后会立即退出终端。
  • man -k exec查看手册得:

  • 语法格式:

    1. int execl(const char pathname, const char arg0, ... /* (char )0 );,execl()函数用来执行参数path字符串所指向的程序,第二个及以后的参数代表执行文件时传递的参数列表,最后一个参数必须是空指针以标志参数列表为空.
    2. int execv(const char path, char const argv[]);,execv()函数函数用来执行参数path字符串所指向的程序,第二个为数组指针维护的程序参数列表,该数组的最后一个成员必须是空指针。
    3. int execlp(const char filename, const char arg0, ... /* (char )0 / );,execlp()函数会从PATH环境变量所指的目录中查找文件名为第一个参数指示的字符串,找到后执行该文件,第二个及以后的参数代表执行文件时传递的参数列表,最后一个参数必须是空指针.
    4. int execvp(const char file, char const argv[]);,execvp()函数会从PATH环境变量所指的目录中查找文件名为第一个参数指示的字符串,找到后执行该文件,第二个及以后的参数代表执行文件时传递的参数列表,最后一个成员必须是空指针。
  • 由于exec函数会取代执行它的进程,一旦exec函数执行成功, 它就不会返回了,进程结束。但是如果exec函数执行失败, 它会返回失败的信息, 而且进程继续执行后面的代码。所以通常exec会放在fork() 函数的子进程部分,来替代子进程执行,执行成功后子程序就会消失, 但是执行失败的话,必须用exit()函数来让子进程退出。

mybash

  • mybsah的实现代码:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#define PATH "/home/" void print_info()
{
uid_t id = getuid();
char *status = "$";
if( id == 0)
{
status = "#";
}
struct passwd *p = getpwuid(id);
if(p == NULL)
{
printf("mybash>>");
fflush(stdout);
return;
} char path[128] = {0};
getcwd(path,256); char *q = "/";
char *s = strtok(path,"/");
while( s != NULL)
{
q = s;
s = strtok(NULL,"/");
}
char hostname[128] = {0};
gethostname(hostname,128);
printf("[%s@%s %s]%s ",p->pw_name,hostname,q,status);
}
int main()
{
while(1)
{
print_info();
char buff[128] = {0};
fgets(buff,128,stdin);
buff[strlen(buff)-1] = 0;
char *myargv[10] = {0};
char *s = strtok(buff," ");
if(s == NULL)
{
continue;
}
myargv[0] = s;
int i = 1;
while((s = strtok(NULL," ")) != NULL)
{
myargv[i++] = s;
}
if(strcmp(myargv[0],"exit") == 0)
{
exit(0);
}
else if(strcmp(myargv[0],"cd") == 0)
{
if(chdir(myargv[1]) == -1)
{
perror("chdir error");
}
continue;
}
pid_t pid = fork();
assert(pid != -1);
if(pid == 0)
{
char path[256] = {0};
if(strncmp(myargv[0],"./",2) != 0 && strncmp(myargv[0],"/",1) != 0)
{
strcpy(path,PATH);
}
strcat(path,myargv[0]);
execv(path,myargv);
perror("execv error");
exit(0);
}
else
{
wait(NULL);
}
}
}
  • 运行结果

20165324_mybash的更多相关文章

随机推荐

  1. swift - UISegmentedControl 和 UIWebView 的用法

    这两个用法比较简单: 具体代码如下: 一.UISegmentedControl 1.UISegmentedControl的声明 var segment = UISegmentedControl() 2 ...

  2. UE4读取脑电波MindWave插件(展示如何使用第三方库制作UE4插件)

    MyEEGPlugin.uplugin { , , "VersionName": "1.0", "FriendlyName": " ...

  3. 关于ARM的内核架构

    很多时候我们都会对M0,M0+,M3,M4,M7,arm7,arm9,CORTEX-A系列,或者说AVR,51,PIC等,一头雾水,只知道是架构,不知道具体是什么,有哪些不同?今天查了些资料,来解解惑 ...

  4. 《C++ Primer Plus》14.4 类模板 学习笔记

    14.4.1 定义类模板下面以第10章的Stack类为基础来建立模板.原来的类声明如下:typedef unsigned long Item; class Stack{private:    enum ...

  5. Runtime 运行时之一:消息转发

    解释一 上一篇文章咱们提到了Runtime的消息传递机制,主要围绕三个C语言API来展开进行的.这篇文章我将从另外三个方法来描述Runtime中另一个特性:消息转发机制. 一.消息转发机制 当向某个对 ...

  6. 总结微信小程序开发中遇到的坑

    总结微信小程序开发中遇到的坑,一些坑你得一个一个的跳啊,/(ㄒoㄒ)/~~ 1,页面跳转和参数传递实例 首先说一下我遇到的需求有一个我的消息页面,里面的数据都是后端返回的,返回的数据大致如下,有一个是 ...

  7. poj_3168 平面扫描

    题目大意 给定平面上N个矩形的位置(给出矩形的左下角和右上角的坐标),这些矩形有些会有重叠,且重叠只会出现矩形的边重合全部或部分,矩形的顶点重合,而不会出现一个矩形的顶点位于另一个矩形的内部.     ...

  8. poj_2352 Treap

    题目大意 对于二维平面上的n个点,给出点的坐标.定义一个点A覆盖的点的个数为满足以下条件的点B的个数:点B的x <= 点A的x坐标,点B的y坐标 <= 点A的y坐标.     给出N个点的 ...

  9. struts2基础---->自定义拦截器

    这一章,我们开始struts2中拦截器的学习. 自定义拦截器

  10. JDBC处理文本和二进制文件

    JDBC支持文本(CLOB)和二进制(BLOB)文件的处理,比如要往数据库里存取文章或者图片.这都是用流的思想来解决的. 来两个Demo看看JDBC是怎么操作文本和二进制文件的. CLOB: pack ...