进程(三)----(进程创建、进程终止、进程等待、进程替换)字符串解析,实现一个minishell

进程控制

1、进程创建

pid_t fork(void);- - - - -创建一个进程(父子进程数据独有,代码共享)

在这里插入图片描述
写时拷贝技术(提高进程创建效率):子进程复制了父进程,一开始与父进程指向同一块物理内存;因此看起来父子进程完全相同;但是进程之间具有独立性,意味着当这块物理内存中数据即将发生改变时会重新给子进程开辟物理内存,将数据拷贝过来,因为子进程应该有自己的数据。按理说每个进程都应该有自己独立的内存空间,但若创建子进程时直接开辟空间拷贝技术,会比较缓慢,并且拷贝过来的数据子进程从来都不用。
代码共享"因为代码段是只读的。
**子进程创建pcb的时候,也会创建页表,虚拟地址空间,然后从父进程pcb中拷贝这些数据过来。

  • vfork创建的子进程,父子进程可以同时运行
  • pid_t vfork(void):创建一个子进程,并且阻塞父进程,直到子进程exit退出或者程序替换之后,父进程才会进行
  • vfork创建子进程效率比较高,因为vfork创建子进程后父子进程会共用同一个虚拟空间。
  • 在这里插入图片描述
  • 早期使用vfork是因为它创建子进程效率高,但是fork实现了写时拷贝之后,创建效率也变高了,并且使用起来更加灵活,因此vfork现在已经很少使用了。

创建子进程优点:让子进程调度另一个程序运行。(有一个任务,父进程可以干,但是怕有风险,万一数据处理出错崩溃了,因此创建一个子进程,让子进程去干)

进程创建的流程:在内核中调用clone接口创建pcb,从父进程pcb拷贝数据进来,写时拷贝技术。

进程终止(退出一个进程)

  • main函数return 退出进程时会退出缓冲区
  • void exit(int status);- - - - - 库函数- - - - - 退出调用进程,将status作为返回值返回给父进程
  • void _exit(int status);- - - - - 系统调用接口- - - - -退出调用进程,将status返回给父进程

库函数和函数调用接口的关系?(库函数封装了系统调用接口,系统调用接口不太好用)

  • exit/return退出时都会刷新缓冲区,_exit退出时直接释放资源,不刷新缓冲区
  • return只会在main函数中的时候才会退出进程,而exit是在任意位置调用都会推出调用进程

以上几种进程退出方式都属于进程的正常退出,但是会根据返回值向父进程表示为什么退出。
在进程退出时也可能异常退出,(如:程序中出现分母为零情况)- - - - -程序崩溃- - - - -程序并没有运行完毕,突然因为某种错误退出了

进程等待(父进程等待子进程的退出,获取子进程的返回值,避免产生僵尸进程)

pid_t wait(int *status);- - - - -阻塞等待任意一个子进程退出,获取子进程的返回值到status指向的空间中,并且释放子进程资源,返回退出的子进程pid。
阻塞:为了完成某个功能发起一个调用,若目前不具备完成功能的条件,则调用不返回一直等待;
非阻塞:为了完成某个功能发起一个调用,若当前不具备完成功能的条件,则立即报错返回;非阻塞操作通常需要循环操作。

*pid_t waitpid(pid_t pid,int status,int options);
pid:若== -1,则表示等待任意一个子进程退出;若 > 0,则表示等待指定的子进程退出。
status:输出型参数,传入一个 int 空间的首地址,获取退出的子进程返回值。
options:选项参数,0- - - 表示默认阻塞等待;WNOHANG- - -表示将waitpid设置为非阻塞,没有子进程已经退出的话就立即报错返回,返回值为0。
waitpid返回值:若等待到了子进程退出则返回子进程的pid;若有子进程,但是没有退出则返回0;出错返回 -1.

  • 非阻塞操作相较于阻塞操作,对CPU利用更加充分,因为不在一直等待,但必须循环进行操作
  • wait和waitpid并不是只处理刚退出的程序,而是对已经退出的进程进行处理(不管什么时候退出的)

int status == status这个变量有四个字节的空间,向函数传入地址
int *status 定义了一个指针变量,有8个字节空间,存放的是另一块空间的地址
返回值的获取:子进程退出的返回值只使用了一个字节保存,并且通wait/waitpid函数的status返回给用户;然而这个status有4个字节,返回值其实保存在低16位的高8位(即:(status>>8)&0xff)
在这里插入图片描述
在这里插入图片描述
首先获取低7位,异常退出信号值,通过是否为0判断进程是否正常退出
00000000 00000000 00000000 01111111 & status = 低7位 0x7f &status
WIFEXITED(status)- - - - - 用于根据status判断进程是否正常退出
WEXITSTATUS(status)- - - - - 从status中取出子进程退出返回值

因为得到进程退出可能是正常退出,也可能是异常退出的。获取一个返回值的前提是这个进程是正常退出的!

判断子进程是否正常退出:

在这里插入图片描述
信号:通知进程发生了某个事件,中断进程当前的操作,去处理这个事件。而操作系统中的信号的体现是一个数字,某个数字表示某个异常事件。程序因为异常退出的时候,则会将这个异常退出事件的信号放置到低7位中。

程序替换

创建子进程的目的:1、让子进程干着和父进程一样的事情,分摊压力; 2、让子进程干其他事情,让子进程背锅
如何让子进程干其他事情?

1、通过fork的返回值进行分流后,子进程的 if(fork()==0)- - - - -子进程干的事情
这种方式存在的缺陷:

  • 程序的耦合度很强,因为所有的功能代码都是在当前程序中编写的,如果想要改变子进程的功能处理流程,需要修改整个代码,然后重新进行编译;导致程序变得非常庞大。

程序替换:替换一个正在调度的程序信息(进程是pcb,负责调度管理程序的运行,运行的数据和代码都在内存中),将另一个程序加载到内存中,然后让原有的pcb不再调度原来的程序,而去调度这个新的程序。
在这里插入图片描述

如何在代码中完成程序替换,让一个进程调度运行另一个程序

在这里插入图片描述
l和v的区别

在这里插入图片描述
有没有e的区别
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
一个进程的环境变量是它的父进程赋予的
我么在终端运行一个程序,这个程序的父进程实际上是shell程序。当我们在shell中输入一个命令,这时候其实shell对这个标准输入进行解析,得到了程序名称;然后创建子进程,并且将子进程的程序替换为当前这个解析出来的程序。

实现一个minishell

  1. 等待标准输入- - - - - fgets();//从标准输入一行数据
  2. 获取到标准输入后,进行字符串解析,得到命名名称以及运行参数;
  3. 创建子进程 - - - - - fork
  4. 子进程中进行程序替换,运行指定的命令- - - - - execvp程序替换(不需要指定程序路径)
  5. 父进程等待子进程命令执行完毕- - - - - wait(避免僵尸进程)

字符串解析
在这里插入图片描述

//将[  ls  -l  -a  ]解析为 [ls] [-l] [-a]
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char buf[]={  ls  -l  -a  };
char* ptr = buf;
char* n_argv[32] = {NULL};
int n_argc = 0;
while(*ptr != '\0')
{
	if(*ptr != ' ')  //当前字符不为空格
	 {
	 	n_argv[n_argc] = ptr;   //argv[0]=[ls  -l  -a  ],argv[1] =[s  -l  -a  ]
	 	n_argc++;
	 	while(*ptr != ' ')       //让ptr把ls走完,走到下一个空格处
	 		ptr++;
	 	*ptr = '\0';  //把s字符后的第一个空格置位字符串结束标准\0,这样argv[0]=[ls]起始是l的位置,结束是s后的\0位置
	 }
	 ptr++;
}
}

实现一个minishell

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    while(1) {
        //增加一个shell提示
        printf("[san@minishell]$ ");
        fflush(stdout);//刷新标准输出缓冲区
        //1. 等待标准输入
        char buf[1024] = {0};
        fgets(buf, 1023, stdin);
        buf[strlen(buf)-1] = '\0'; //buf[...]='\n'
        //2. 对输入数据进行解析
        char *argv[32] = {NULL};
        int argc = 0;
        char *ptr = buf;
        // [    ls    -a    -l    ]
        while(*ptr != '\0') {
            if (*ptr != ' ') {
                argv[argc] = ptr;
                argc++;
                while(*ptr != ' ' && *ptr != '\0') {
                    ptr++;               
                }
                *ptr = '\0';
            }
            ptr++;
        }
        argv[argc] = NULL;//最后一个参数的下一个位置置NULL
        //3. 创建子进程 4. 在子进程中程序替换
        pid_t pid = fork();
        if (pid == 0) {
            //execvp(char *file, char *argv[])  file--新程序名称 
            execvp(argv[0], (char**)argv);//程序替换成功就去运行新程序了,32行以后就不会运行了
            //能够走到第33行,那么肯定程序替换失败了
            perror("execvp error");//打印上一次系统调用接口使用的错误原因
            exit(0);
        }
        //5. 进程等待
        wait(NULL);
    }
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值