Linux_进程控制并实现minshell

关于进程控制

主要是四个部分:
进程创建、进程终止、进程等待、程序替换
然后模拟实现一个minshell

进程创建

fork()函数

在linux中fork函数就是从已存在的进程中新建一个新进程,新进程为子进程,而已存在的
原进程为父进程
#include <unistd.h>
pid_t fork(void)
返回值:新建进程返回0,父进程返回子进程id,出错返回-1
(子进程创建成功后,代码执行的位置:父进程执行到哪,子进程就从那开始执行,至于谁先执行
完全由调度器决定)

#include <unistd.h>
#include <stdio.h>
int main()
{
        pid_t pid;
        printf("before: pid = %d \n",getpid());
        if ((pid = fork()) == -1)
        {
                printf("error\n");
        }
        printf("after: pid = %d,fork return %d\n",getpid(),pid);
        sleep(1);
        return 0;
}

在这里插入图片描述
fork()调用失败原因: 系统中由太多的进程、实际用户的进程数超过了限制

那调用fork()函数时,系统内核在做什么呢?
第一步:分配新的内存块和内核数据结构给子进程
第二步:将父进程部分数据结构内容拷贝给子进程
第三步:添加子进程到系统进程列表中
最后:fork()返回,开始调度器调度

写时拷贝:
一般情况下,父子代码共享,父子进程再不写入时,物理内存中的数据依旧共享,当其中一方试图写入时,便以写时拷贝的方式各自一份副本(将原来的数据进行拷贝,存入修改的数据里面)
在这里插入图片描述

vfork()函数

vfork() 的存在是还在fork()函数没有写时拷贝的时候,因为哪个时候创建一个子进程的成本太大
若一下创建太多进程,程序效率一定下降,这时候就提出了vfork() ,其实实现原理就是 父子进程共享一个资源,即使修改了内容,或main()函数推出了,都不会开辟一个新的空间,这里就会出现一个问题:如果子进程没有使用exit()退出,在函数栈上,资金运行结束了,main()的函数栈就被子进程释放了,父进程使用时就访问不到了
所以子进程退出一定要使用exit()退出

vfork同样用于创建一个子进程,当其子进程与父进程共享地址空间,
而fork()的子进程具有独立的地址空间

vfork()保证子进程先运行,在它调用exec或exit之后 父进程才可能被调度运行

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


int glob = 100;

int main(void)
{
        pid_t pid;

        printf("%d\n",glob);
        if ((pid = vfork()) == -1)
        {
                exit(0);
        }
        if( pid == 0)
        {
                sleep(5);
                glob = 200;
                printf("child glob = %d \n", glob);
                exit(0);
        }
        else
        {
                printf("parent glob %d\n", glob);
        }

        return 0;
}

在这里插入图片描述
fork()与vfork()的区别:

fork()函数父子进程交替运行,vfork()函数,子进程运行,父进程阻塞,直到子进程退出
但vfork()一定要用exit或execl退出

fork()实现了写时拷贝,而vfork()共享地址空间

fork()有写时拷贝也没有vfork的性能高,但是给个系统上vfork都存在一些问题,所以不推荐使用

clone()

clone()函数将部分父进的资源的数据结构进行赋值,复制那些资源可通过函数参数设定

int clone(int (fn)(void), void* child_stack, int flags, void *arg)

fn为函数指针,指向一个函数体,即想要创建进程的静态程序,
child_stack就是子进程分配系统堆栈的指针,
arg就是传给子进程的参数,
flags为要复制资源的标志

进程终止

进程退出:
正常退出(从main返回,调用exit,_exit):代码运行完毕,结果正确/不正确
异常退出(ctrl + c 信号终止):代码异常终止

_exit()函数
#include <unistd.h>
void _exit(int status);
status定义了进程终止的状态,父进程通过wait来获取该值,虽然status为int类型,但只有低八位才可以被父进程所用,所以_exit(-1)时,在终端执行$? 返回值为255

exit函数
#include <unistd.h>
void exit(int status)

exit最后也会调用_exit:
执行用户通过atexit或on_exit定义的清理函数
关闭所有打开的流,所有的缓存数据均被写入
调用_exit

return退出

执行return n 就相当于 exit(n),因为调用main的运行时函数会将main的返回值当作exit的参数

进程等待

进程等待的必要性:
子进程退出,父进程不管不顾,就会造成僵尸进程,进而早晨内存泄露
进程一旦形成僵尸状态,那就很麻烦, 即使是kill -9 也不行,因为僵尸进程已经“死了”
并且我们需要直到父进程给子进程的任务完成情况

而进程等待 就是父进程通过进程等待的方式来回收子进程的资源,获取子进程的退出信息

wait()

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* status);

返回值:
成功返回等待进程pid,失败返回-1
参数:
输出性参数,获取子进程退出状态,不关心则设置为NULL

waitpid()

pid_t waitpid(pid_t pid, int* status, int options);

返回值:
当正常返回的时候waitpid返回收集的子进程的进程ID
如果设置了选项WNOHANG,而调用中waitpid发现没有已推出的子进程可收集,则返回0
如果调用中出错,则返回0,这时errno会被设置成相应的值以指示错误所在
参数:
pid:
pid = -1,相当于wait 等待任一子进程
pid>0,等待其进程ID与pid相等的子进程

status:
WIFEXITED:若为正常终止子进程返回的状态,则为真(查看进程是否正常退出)
WEXITSTATUS:若WIFEXITED非零,提取子进程推出码(查看进程退出码)

options:
WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待,若正常结束,则返回子进程的ID

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>

int main()
{
        pid_t pid;
        if ((pid = fork()) == -1)
        {
                perror("fork"),exit(1);
        }

        if (pid == 0)
        {
                sleep(25);
                exit(10);
        }
        else
        {
                int st;
                int ret = wait(&st);
                if (ret > 0 && (st & 0x7F) == 0)
                {
                        printf("child exit code : %d\n", (st>>8)&0xFF);
                }
                else if (ret > 0)
                {
                        printf("sig code: %d\n",st&0x7F);
                }
        }
        return 0;
}

在这里插入图片描述

进程的阻塞等待方式

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
int main()
{
        pid_t pid;
        pid = fork();
        if (pid < 0)
        {
                printf("fork error\n");
        }
        else if (pid == 0)
        {
                printf("child is run ,pid = %d\n",getpid());
        }
        else
        {
                int status = 0;
                pid_t ret = waitpid(-1, &status, 0);
                printf(" wait \n");
                if (WIFEXITED(status) && ret == pid )
                {
                        printf("wait child 5s success, child return code is %d\n"
                        ,WEXITSTATUS(status));
                }
                else
                {
                        printf("wait child failed, return \n");
                        return 1;
                }
        }
        return 0;
}

在这里插入图片描述
进程的非阻塞等待方式

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
int main()
{
        pid_t pid;
        pid = fork();
        if (pid < 0)
        {
                printf("fork error\n");
                return 1;
        }
        else if (pid == 0)
        {
                printf("child is run ,pid = %d\n",getpid());
                sleep(5);
                exit(1);
        }
        else
        {
                int status = 0;
                pid_t ret = 0;
                do
                {
                        ret = waitpid(-1, &status, WNOHANG);
                        if (ret == 0)
                        {
                                printf("child is running\n");
                        }
                        sleep(1);
                }while(ret == 0);
                printf(" wait \n");
                if (WIFEXITED(status) && ret == pid )
                {
                        printf("wait child 5s success, child return code is %d\n"
                        ,WEXITSTATUS(status));
                }
                else
                {
                        printf("wait child failed, return \n");
                        return 1;
                }
        }
        return 0;
}

在这里插入图片描述
进程程序替换:

替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),资金往往要调用一种exec函数以执行另一个程序,当今从调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec前后该进程的id并未改变
在这里插入图片描述
exec函数其实有六种exec开头的函数,execl、execlp、execle、execv、execvp、execve
这里就不一一具体解释了,只要掌握规律就好记
后缀加:
l(list):表示参数采用列表
v(vector):表示用数组
p(path):有p自动搜索环境变量PATH
e(env):表示自己维护环境变量
在这里插入图片描述
实际只有execve才是系统真正调用的,其他五个最终都要调用execve

做一个简易的shell


#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

//获取命令行
//解析命令行
//建立一个子进程(fork)
//替换进程(execvp)
//父进程等待子进程退出

char * argv[8];
int argc = 0;

void do_parse(char *buf)
{
        int i;
        int status = 0;
        for (argc = i = 0; buf[i]; i++)
        {
                if(!isspace(buf[i]) && status == 0)
                {
                        argv[argc++] = buf + i;
                        status = 1;
                }
                else if (isspace(buf[i]))
                {
                        status = 0;
                        buf[i] = 0;
                }
        }
        argv[argc] = NULL;
}

void do_execute(void)
{
        pid_t pid = fork();

        switch(pid)
        {
                case -1:
                perror("fork");
                exit(EXIT_FAILURE);
                break;
                case 0:
                execvp(argv[0], argv);
                perror("execvp");
                exit(EXIT_FAILURE);
                default:
 				{
                        int st;
                        while (wait(&st) != pid);
                }
        }
}

int main()
{
        char buf[1024] = {};
        while(1)
        {
                printf("myshell > ");
                scanf("%[^\n]%*c", buf);
                do_parse(buf);
                do_execute();
        }
        return 0;
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值