1、ps能做什么
(1)什么是进程
进程=运行中的程序,一个程序是存储在文件中的机器指令序列,一般它是由编译器将源代码编译成二进制格式,运行一个程序意味着将这个机器指令序列载入内存然后让处理器逐条执行这些指令。一个可执行程序是一个机器指令及其数据的序列,一个进程是程序运行时的内存空间和设置。
(2)ps能做什么
进程存在于用户空间,用户空间是存放运行的程序和它们的数据的一部分内存空间。命令ps(process status)可以查看用户空间的内容,显示当前用户空间所有的进程,总共是四列内容,分别是进程ID、与之相连的终端、已运行时间、对应的命令。每个进程都有一个唯一标识它的数字就称之为进程ID。
2、psh是如何实现的
(1)shell是什么
shell是一个管理进程和运行程序的程序。shell有三个主要功能:
- 运行程序:shell将被编译成机器语言的普通程序载入内存并运行,shell看成是一个程序启动器
- 管理输入输出:shell使用<、>和|符号可以将输入、输出重定向,这样就可以告诉shell将进程的输入和输出连接到一个文件或是其他的进程
- 可编程:shell同时也是带有变量和流程控制的编程语言
(2)shell是如何运行程序的
一个shell的主循环执行下面4步:
- 用户键入a.out,shell获取命令
- shell建立一个进程来运行这个程序
- shell将程序从磁盘载入
- 程序在它的进程中运行直到结束
实现shell的思路:shell程序先fork出一个子进程,然后子进程使用execvp运行shell需要执行的程序命令,父进程也就是当前shell程序wait等待子进程运行结束,子进程结束后回到父进程继续执行。
(3)一个程序如何运行另一个程序——execvp
exec系统调用从当前进程中把当前程序的机器指令清除,然后在空的进程中载入调用时指定的程序代码,最后执行新的程序。exec调用进程的内存分配使之适应新的程序对内存的要求。相同的进程,不同的内容。
execvp程序调用:
- execvp():在指定路径中查找并执行一个文件,result=execvp(const char *file,const char *argv[]),file是要执行的文件名,argv是字符串数组,将argv传给执行的程序。execvp载入由file指定的程序到当前进程,然后试图运行它,execvp将以NULL结尾的字符串列表argv传给程序。execvp在环境变量PATH所指定的路径中查找file文件。
(4)如何建立新的进程——fork
一个进程调用fork来复制自己。
进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构
- 复制原来的进程到新的进程
- 向运行进程集添加新的进程
- 将控制返回给两个进程,两个进程有相同的代码,运行到同一行有相同的数据和进程属性
fork系统调用:
- fork():创建子进程, pid_t result=fork(void),在子进程中fork返回0,在父进程中fork返回子进程ID。
(5)父进程如何等待子进程的退出——wait
进程调用wait等待子进程的结束。
wait系统调用:
- wait():父进程等待子进程结束,pid_t result=wait(int *statusptr),子进程运行结束后的运行结果保存到statusptr,最后结束的子进程ID返回给result,如果调用的进程没有子进程也没有得到终止状态值,返回-1.。wait系统函数挂起调用它的进程直到得到这个进程的子进程已经退出或被杀死,对wait的调用立即返回。wait返回结束的子进程的PID。wait将退出状态或者信号序列复制到statusptr指向的整数中。这个值是个16位的数,0-6前7位是记录信号序列,如果进程被杀死,那么内核将信号序列存放在该序位,第7位指明发生错误并产生了内核映像,8-15最后8位是记录退出值,如果子进程调用exit退出,那么内核把exit的返回值放入该序位。
(6)exit的细节
exit是fork的逆操作,进程通过exit来停止运行。fork创建一个进程,exit删除进程。exit刷新所有的流,调用由atexit和on_exit注册的函数,执行当前系统定义的其他与exit相关的操作,然后调用_exit。
_exit是一个内核操作,这个操作处理所有分配给这个进程的内存,关闭所有这个进程打开的文件,释放所有内核用来管理和维护这个进程的数据结构。
子进程传给exit的参数的处理过程:子进程的弥留之言被存放在内核直到父进程通过wait系统调用取回这个值,如果父进程没有在等这个值,那么它将被保存在内核直到父进程调用wait,那时内核将通告这个父进程子进程的结束,并转达子进程的弥留之言。那些已经死亡但是还没有给exit赋值的进程被称为幽灵进程。ps会列出这些进程并标记为defunct。
系统调用_exit终止当前进程并执行所有必须的清理工作:
- 关闭所有文件描述符和目录描述符
- 将进程的PID置为init进程的PID
- 如果父进程调用wait或waitpid来等待子进程结束,则通知父进程
- 向父进程发送SIGCHLD
如果父进程在子进程之前退出,那么子进程将能继续运行,子进程将成为init进程的子女。
_exit系统调用:
- _exit():终止当前进程,_exit(int status)
3、自己编写一个psh
psh1.c
使用系统调用execvp实现的简单shell程序,缺陷:运行一个命令就退出了
#include<stdio.h>
#include<signal.h>
#include<string.h>
#include<stdlib.h>
#define MAXARGS 20
#define ARGLEN 100
int excute(char *arglist[])
{
execvp(arglist[0],arglist);
perror("execvp");
exit(1);
}
char *makestring(char *buf)
{
char *cp;
buf[strlen(buf)-1]='\0';
cp=malloc(strlen(buf)+1);
if(cp==NULL)
{
fprintf(stderr,"no memory\n");
exit(1);
}
strcpy(cp,buf);
return cp;
}
int main()
{
char *arglist[MAXARGS+1];
int numargs=0;
char argbuf[ARGLEN];
while(numargs<MAXARGS)
{
printf("Arg[%d]?",numargs);
if(fgets(argbuf,ARGLEN,stdin)&&*argbuf!='\n')
arglist[numargs++]=makestring(argbuf);
else
{
if(numargs>0)
{
arglist[numargs]=NULL;
excute(arglist);
numargs=0;
}
}
}
return 0;
}
psh2.c
完整的shell程序
#include<stdio.h>
#include<signal.h>
#include<string.h>
#include<stdlib.h>
#define MAXARGS 20
#define ARGLEN 100
int excute(char *arglist[])
{
int pid,exitstatus;
pid=fork();
switch(pid)
{
case -1:
perror("fork failed");
exit(1);
case 0:
execvp(arglist[0],arglist);
perror("execvp");
exit(1);
default:
while(wait(&exitstatus)!=pid)
;
printf("child exited with status %d,%d\n",exitstatus>>8,exitstatus&0377);
}
}
char *makestring(char *buf)
{
char *cp;
buf[strlen(buf)-1]='\0';
cp=malloc(strlen(buf)+1);
if(cp==NULL)
{
fprintf(stderr,"no memory\n");
exit(1);
}
strcpy(cp,buf);
return cp;
}
int main()
{
char *arglist[MAXARGS+1];
int numargs=0;
char argbuf[ARGLEN];
while(numargs<MAXARGS)
{
printf("Arg[%d]?",numargs);
if(fgets(argbuf,ARGLEN,stdin)&&*argbuf!='\n')
arglist[numargs++]=makestring(argbuf);
else
{
if(numargs>0)
{
arglist[numargs]=NULL;
excute(arglist);
numargs=0;
}
}
}
return 0;
}