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. Spring中通过构造方法传参数

    1.实体类 package com.xbq.bean;import java.util.Timer; /** * @ClassName: Student * @Description: TODO 学生 ...

  2. callable()

    callable() 用于判断一个对象是否是可调用的,函数或类都可以被调用 In [1]: callable('a') Out[1]: False In [2]: def fun(): ...: pa ...

  3. js禁止img拖动

    其实只需要一句代码即可,那就是阻止元素的默认事件: <body> <img src="./../imgs/cat.jpg" id="test" ...

  4. 使用WdatePicker获取比当前时间大的写法

    <input name="today.date" class="Wdate" type="text" onClick="Wd ...

  5. mysql增删改查基本语句

    mysql的增删改查属于基本操作,又被简称CRUD,其中删用的较少,毕竟这个功能给用户是是非常危险的,就是客户删除的数据也没有真正的删除,其中查询是十分常用的. 1 mysql数据库增加:create ...

  6. 使用Android Studio调试内存问题

    http://blog.csdn.net/yutao52shi/article/details/50055669 前言 内存问题对于Android开发者是永远的痛.如果一个android程序员说他没有 ...

  7. LCA(离线算法)

    hdu4547 CD操作 Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) To ...

  8. 310实验室OTL问题----将写好的C++文件转换成Python文件,并将数据可视化

    如图:文件夹 第一处:optimizer文件夹下的:optimizer.h文件中添加你所写代码的头文件  #include <OTL/Optimizer/Reference-NSGA-II/Re ...

  9. C++程序风格的思考

    转载自:http://www.cppblog.com/weiym/archive/2013/04/27/199781.html 发现厚积薄发中有很多值得学习的东西 故引用之: 最近有机会看号称是公司最 ...

  10. Jenkins升级、迁移、备份

    1.升级 下载新版Jenkins.war文件,替换旧版本war文件,重启即可. Jenkins.war文件的位置一般为/usr/lib/jenkins/Jenkins.war. 2.迁移.备份 Jen ...