实验三:linux进程管理
1)实现一个模拟的shell
问题:编写三个cmd1.c
、cmd2.c
、cmd3.c
程序,功能自定。需要一个仿shell的程序和实现类似执行grep
、find
等命令的程序。通过在仿shell中输入命令(字符串和参数),创建子进程来执行相应的命令程序,父进程需要等待子进程处理结束后,才能再去查看是否有新的命令。输入“exit
”退出shell程序。输入无效命令则显示“Command not found”
,等待下一个新命令。
前说下,刚开始把问题看复杂了,除了要求之外还写了cd
、pwd
、exit
、mkdir
,所以可以自行删改(不麻烦),或者有兴趣可以加更多的命令玩玩 : )
处理方法:
myshell.c
:
- 提供循环输入命令模块
- 解析命令
- 判断命令是内置命令还是外置命令(可以不用)
- 内置命令直接调用函数,外置命令用
fork()
来创建一个子进程, 在进程中执行命令程序采用exec族的函数来调用函数执行。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/syscall.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#define MAX_BUF 80
#define MAX_CMD 80
#define MAX_ARG_NR 16
int cmd_len; //输入的命令长度(包含换行符)
//////////////////////////////////////////////////////////////////////////
int main()
{
char buf[MAX_BUF];
char command[MAX_CMD+1];
system("clear");
printf("Songshell: support cd/pwd/mkdir/exit/find/grep\n");
while(1){
getcwd(buf, sizeof(buf));
printf("➜ [%s] ", buf);
fflush(stdout); //输出上一行内容
memset(command,0, sizeof(command));
if((cmd_len = read(0, command, MAX_CMD)) < 2) //仅有回车,cmd_len=1
printf("Command Error! Pleace Try Again\n");
else{
ToProcess(command);
}
}
return 0;
}
ssize_t read(int fd, void *buf, size_t count);
read(0, command, MAX_CMD))
从标准输入文件中读取最大长度为MAX_CMD
并带有回车的字符串写入command
,当除了回车还有其他字符时,调用ToProcess(command)
//执行命令
int ToProcess(const char *command)
{
char *argv[MAX_CMD];
char buf[MAX_CMD];
strcpy(buf, command);
buf[cmd_len-1] = ' ';
buf[cmd_len] = '\n';
ParsePalam(buf, argv);
if(argv[0] == NULL) //命令为空
return 0;
if(!JudgeBuildin(argv)){
//不是内置命令
pid_t child = fork();
if(child > 0){
int status = 0;
wait(&status);
}
else if(child == 0){ //子进程
if(!strcmp(argv[0], "mkdir"))
execl("/home/exp3/myshell/mkdir_cmd", argv[1], NULL);
else if(!strcmp(argv[0], "command1"))
execl("/home/exp3/myshell/command1", argv[0], NULL);
else if(!strcmp(argv[0], "command2"))
execl("/home/exp3/myshell/command2", argv[0], NULL);
else if(!strcmp(argv[0], "command3"))
execl("/home/exp3/myshell/command3", argv[0], NULL);
else if(!strcmp(argv[0], "find"))
execv("/usr/bin/find",argv);
else if(!strcmp(argv[0], "grep"))
execv("/usr/bin/grep", argv);
}
else if(child == -1){
printf("failed: no child process is created!\n");
}
}
return 0;
}
P7-8:为了让后面ParsePalam()
函数更好解析输入的命令参数。在最后一个参数的后面添加空格, 和其他参数结构保持一致(这里不加,解析会出错)。
P9:解析完参数,包括命令及参数都按输入顺序存入char *argv[]
指针数组中,调用形式为argv[0]
,argv[1]
..
P12:一般而言,在一个脚本里执行一个外部命令(普通的可执行文件)时,shell 会 fork 出一个子进程,然后再用 exec族函数来执行这个程序;但是,bash shell 的内置命令(builtin)却不会这样,它们是直接执行的。所以,等价的内置命令的执行速度会比执行外部命令要来的快。
可以通过type或者which命令来查看命令类型:提到builtin
就是内置,打印出目录如/usr/bin/xxxx
这样的就是外置命令
如果不是内置命令就要涉及创建子进程和执行外部程序的实现了
通过终端输入man 2 fork
查看fork的编程手册。
P15:child>0
,返回的是子进程的PID号,说明当前是父进程,按要求,父进程不需要做什么事,等着就完事了。man 2 wait
,看下头文件,函数用最简单的wait(int *wstatus)
P19:child == 0
,说明是子进程,于是要调用外部程序了,man exec
查看exec族的相关信息。exec族博客
拿P21的execl("/home/exp3/myshell/mkdir_cmd", argv[1], NULL);
举例,execl的参数依次是,调用程序的路径名, 参数0,参数1,.....,NULL结尾。这里的argv[1]就是要创建的目录名
mkdir_cmd.c
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
if(mkdir(argv[0], 0755) == -1){
printf("mkdir failed\n");
}
return 0;
}
提醒,main
参数一定别忘记了argc
,血泪教训,参数就是顺序写入到argv
中,我只传入了一个参数。
P29:execv("/usr/bin/find",argv);
,execv不像execl要一个一个传参数,而是直接提供指针数组就行了。因为find命令可以带很多参数就用这个了
P33:child == -1
,fork
失败
注意:因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。所以创建子进程来提供exec执行命令程序的空间。(如下图可以看到,execl调用成功后不会打印success)
//解析命令及参数
int ParsePalam(char *buf, char** argv)
{
int argv_idx, argc;
argv_idx = argc = 0;
while(argv_idx < MAX_ARG_NR) //初始化
argv[argv_idx++] = NULL;
while(1){
while(*buf == ' ') //跳过空格
buf++;
if(*buf == '\n') //碰到回车循环结束
break;
argv[argc] = buf; //记录参数首地址
while(*buf != ' ' && *buf) //确定参数结束地址
buf++;
*buf++ = '\0'; //添加字符串结束符
if(argc > MAX_ARG_NR){ //超过最大参数数量error
printf("argc is too large!\n");
return -1;
}
argc++;
}
return argc; //返回参数数量
}
解析参数的方法是用《操作系统真相还原》里的方法。
主要思路是argv[i]
记录第i个参数的首地址,并把参数后面的空格换成结束符
//判断是否是内置命令
int JudgeBuildin(char **argv)
{
if(!strcmp(argv[0], "cd")){
if(chdir(argv[1])) //return -1 means error
printf("dir isn't exist\n");
return 1;
}
else if(!strcmp(argv[0], "pwd")){
char buf[MAX_BUF];
getcwd(buf, sizeof(buf));
printf("%s\n", buf);
return 1;
}
else if(!strcmp(argv[0], "exit") || !strcmp(argv[0], "q") ){
exit(0);
}
return 0;
}
没啥难度,也不是实验要求。