进程控制 & 编写简易shell

1 进程创建
fork
创建进程成功,子进程返回0,父进程返回子进程的进程ID
在创建进程fork过程中,(1)分配新的内存块和内核数据结构给子进程
             (2)子进程拷贝父进程的代码和数据
             (3)子进程可以执行自己的代码
vfork
【注】:1 vfork也是创建进程,和fork类似
     2 vfork创建的子进程和父进程共享一个地址空间(即:对于全局变量,子进程对其进行修改,父进程也改变了)
     3 vfork保证子进程先运行, 子进程调用exec或_exit后 ,父进程才开始运行。
通过一个小例子看一下vfork:
1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5
  6 size_t a = 10;
  7
  8 int main()
  9 {
10     //1 vfork保证子进程先运行,父进程后运行
11     //2 vfork创建的子进程和父进程共享地址空间
12     pid_t pid = vfork();
13     if(pid == 0)//child
14     {
15         printf("this is child,pid = %d,ppid = %d\n",getpid(),getppid());
16         a = 200;
17         printf("a = %d,&a = 0x%x\n",a,&a);
18         sleep(1);
19         _exit(0);
20         // exec
21     }
22     else if(pid > 0)//father
23     {
24         printf("this is father,pid = %d,ppid = %d\n",getpid(),getppid());
25         printf("a = %d,&a = 0x%x\n",a,&a);
26         sleep(5);
27     }
28     else
29     {
30         perror("vfork");
31     }
32     return 0;
33 } 

我们可以看到,在子进程中更改a的值,父进程中a的值也发生改变。

2 进程终止
进程退出的三种情况:(1)代码跑完了,结果正确;
            (2) 代码 跑完了,结果不正确;
            (3) 代码 没跑完。
进程的终止:三种正常终止:(1)在main函数调用return
               (2)调用exit
               (3)调用_exit
       两种异常终止:(1)调用abort,产生SIGABORT信号,是异常终止的一个特例;
               (2)进程接收到某个信号,如:Ctrl+c
【注】:exit在任何地方都可以表示进程终止,return只有在main函数中才表示进程终止。

_exit:参数status是进程的退出状态,没有返回值


exit:

exit和_exit的区别:
我们先通过一个例子感受一下他们的区别:

1 _exit是强制终止进程,简单粗暴

2 exit在终止进程之前,会执行用户定义的清理函数,刷新缓冲,关闭流等工作,最后再调用_exit终止进程。

3 进程等待

为什么要进程等待?
1 如果子进程退出,父进程没有得到子进程的退出信息,会造成子进程变为“僵尸进程”, 僵尸进程会浪费资源、内存泄漏,有很大的危害;
2 父进程通过等待的方式,回收子进程资源,获取子进程的退出信息;
3 进程等待可以保证父子进程的退出顺序。

进程等待的两种方式:1 阻塞式等待:“干等”就是在等待的时候,什么事情都不做
            2 非阻塞式等待:需要等待,做其他事情,检测是否需要等待,需要等待,做其他事情......

wait和wait_pid:

wait:

参数:status 输出型参数,获取子进程的退出信息,如果不关心,可以设置为NULL
返回值:等待成功,返回子进程的进程ID,等待失败,返回-1
运行结果:

wait_pid:
参数:pid: pid = -1 等待任意一个进程,和wait等效
        pid > 0 等待指定进程
    status:输出型参数,获取子进程的退出信息,如果不关心,可以设置为NULL
    options:默认为0:阻塞式等待
         WNOHANG:非阻塞式等待
返回值:正常返回,返回子进程的进程ID;
     设置WNOHANG选项,如果检测到子进程仍在运行,返回0;
     等待失败:返回-1

wait_pid 以阻塞方式等待

运行结果:

wait_pid 以非阻塞方式等待

运行结果:

退出信息:status

通过上面的代码可以看到,我通过对status进行位运算,打印出了进程退出的信号和状态,

那么,status退出信息这个参数到底是什么样的呢?
status并不是一个简单的整型,可以当做位图来看它:

4 进程替换
进程替换原理:在fork创建子进程之后,子进程往往通过进程替换,执行另一个程序,调用exec进程替换后,并没有创建新的进程,只是替换了代码和数据,并将栈和堆重新加载,所以其进程ID并没有改变
六个替换函数:
#include <unistd.h>
 int execl(const char *path, const char *arg, ...); 
 int execlp(const char *file, const char *arg, ...);
 int execle(const char *path, const char *arg, ..., char * const envp[]);
 int execv(const char *path, char *const argv[]);
 int execvp(const char *file, char *const argv[]);
 int execve(const char *filename, char *const argv[], char *const envp[]);

参数:path 可执行文件的目录
          arg 替换的可执行文件,以NULL结束
          file 文件名,会自动在PATH环境变量中找
          envp 环境变量,以NULL结束(替换后不会继承原来的环境变量)

应用实例:


【注】:1 exec替换函数的参数只是做替换,不做参数的检验
     2 六个exec函数中,execve是系统调用,其余五个函数为库函数

5 实现简易的myshell
思路:1 显示用户名、主机名、当前目录作为myshell输入的请示
   2 从标准输入里面读取一行字符串
   3 解析字符串,解析出指令和参数
  (1)借助strtok字符创切分函数,以“ ”对字符串进程切分
  (2)手动切分,遍历字符串,保存后面的字符创,将该处空格置成“\0”, 将切分出的该字符串保存在一个指针数组中
   4 创建子进程,子进程程序替换,父进程等待

实现:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <pwd.h>
#include <sys/utsname.h>

#define FILEPATH_MAX (80)

//解析指令
void Analysis(char* str,char* argv[])
{
    char* p = strtok(str," ");
    int argc = 0;
    while(p != NULL)
    {
        argv[argc++] = p;
    
        //argv[argc] = p;
        //printf("%s\n",argv[argc]);
        //argc++;
        p = strtok(NULL," ");
    }
    argv[argc] = NULL;
}

//进程替换
void Exec(char* argv[])
{
    pid_t pid = fork();
    if(pid == 0)//child
    {
        execvp(argv[0],argv);
        //进程替换失败了,打印错误信息,并退出
        perror("argv[0]");
        exit(1);
    }
    else if(pid > 0)//father
    {
        wait(NULL);
    }
    else
        perror("fork");
}

int main()
{
    //获得用户名
    struct passwd *pwd;
    pwd = getpwuid(getuid());

    //获得主机名
    char computer[256];
    gethostname(computer,256);
    int i = 0;
    while(computer[i] != '.')
    {
        i++;
    }
    computer[i] = 0;

    //获得当前路径
    char* file_path_getcwd;
    file_path_getcwd = (char*)malloc(FILEPATH_MAX);
    getcwd(file_path_getcwd,FILEPATH_MAX);
    char* p = strtok(file_path_getcwd,"/");
    int argc = 0;
    char* argv[FILEPATH_MAX] = {p};
    while(p != NULL)
    {
        argv[argc++] = p;
        p = strtok(NULL,"/");
    }
    argv[argc] = NULL;

    char buf[1024] = {0};
    while(1)
    {
        printf("[%s@%s %s] ",pwd->pw_name,computer,argv[argc - 1]);
        fflush(stdout);
        gets(buf);
        char* argv[10];
        //解析输入的buf,解析出指令和参数
        Analysis(buf,argv);
        //fflush(stdout);
        //进程替换
        Exec(argv);
    }
    return 0;
}
运行:

猜你喜欢

转载自blog.csdn.net/weixin_39294633/article/details/80460832