7进程控制(补充:最后代码未实现)

进程控制:创建、终止、等待、替换

 

一、创建

如何创建一个进程??

pid_fork(void);           写时拷贝的方式创建一个新的子进程出来,父子进程代码共享数据独有

pid_vfork(void);         创建一个新的子进程,父子进程共用同一个虚拟地址空间(不常用)

        因为vfork创建子进程后,父子进程共用虚拟地址空间,因此使用的是同一个栈,因此一个进程对数据做的改变,也会体会到另一方。但是这样就会有一个大隐患,调用栈混乱因此vfork创建子进程后,父进程是要阻塞的,直到子进程exit退出或者进行了程序替换,创建了自己的地址空间,以及各项数据。

        vfork是早期为了提高进程创建效率而提出来的,但是在fork实现了写时拷贝技术之后就很少用了。

 二、终止

如何退出一个程序??

退出场景:正常退出(符合预期退出,不符合预期退出),异常退出

int main(){
    int a;
    char *null_ptr = NULL;
    scanf("%d",&a);
    if(a > 10){
        printf("%s",null_ptr);
        return 1;
    }else{return 0;}
}

获取上一次系统调用接口的错误原因的接口:

    perror("fork error");//获取上一步系统调用接口的错误原因
    printf("fork error:%s\n",strerror(errno));//这个就等价于上面的perror

void perror(char *dsc);
char *strerror(int errno);
int errno;//全局变量,用于存放上一次系统调用的错误原因代码

如何退出一个进程:

在main函数中return;(注意:return只有在main中使用的时候是退出程序,否则在其他函数中只能表示退出对应的函数)

库函数:

void exit(int return_val);

可以在程序代码的任意位置进行调用,用于退出程序的运行,退出前会刷新缓存区数据到文件

系统调用接口:

void ——exit(int return_val);

可以在程序代码的任意位置进行调用,用于退出程序的运行,直接退出释放资源


库函数和系统调用接口是什么关系??

库函数是大佬们针对典型应用功能对系统调用接口进行封装,便于使用。


缓冲区和缓存的区别??

buff-缓冲区:相对于文件来说就是数据写入文件前,先到缓冲区中,积累到成为大数据一次性刷新缓冲区写入文件中,减少io次数

cache-缓存:相对于文件来说从文件中读取数据,一次性拿出的是一块磁盘块的数据,放到内存中,下次读取数据的时候先从缓存中对比


三、等待

        父进程创建子进程后,等待子进程退出,获取子进程的退出返回值,释放子进程的资源,避免僵尸进程的产生。

      僵尸进程的产生:子进程先于父进程退出,为了保存退出原因,因此没有完全释放资源,而是通知父进程,但是父进程没有进行处理。(因为父进程没有感受到子进程退出的通知,因此干脆让父进程创建子进程之后一直等着,避免因为没有收到通知而错过)  

等待操作:

#include<sys/wait.h>

pid_t wait(int *status);//阻塞等待任意一个子进程退出,使用status作为输出参数获取子进程的退出返回值

成功则返回处理的这个退出的在机场的进程ID;失败返回-1

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

        pid_t pid:指定要等待的子进程的ID;-1表示等待任意一个子进程

        int *status:输出,用于向外界返回退出子进程的返回值

        int options:操作选项,0——阻塞等待;WNOHANG——如果没有子进程已经退出,则waitpid接口则直接返回,不阻塞。(控制是否阻塞等待)

        返回值:成功等待子进程退出,则返回子进程ID;如果当前没有子进程返回0;出错-1

wai、waitpid接口都是等待一个子进程退出,那如果等待前就有子进程退出,那么会被处理么?

        答:会,如果调用wait&waitpid的时候已经有子进程退出,则直接进行处理,处理完毕接口返回

waitpid的非阻塞使用,一定要使用循环操作,否则操作就有可能没完成


进程的退出返回值,实际上只用了一个字节进行保存,也就是说只能返回0~255之间的数据,再多就会截断

 进程有俩种退出方式:

        正常退出:exit,或者return

        异常退出:中途崩溃

一个进程如果异常退出,是没有返回值的,这时候保存退出返回值的空间中的数据就是不可知的,获取到的退出返回值也是没有意义的。

在status获取的数据中,低7位是进程的异常退出码,如果不为0就表示进程是异常退出的,因此在进程获取返回值之前,应该先获取异常退出码,看看进程是否异常退出,非异常退出才会去获取退出返回值

异常退出码的获取:4字节中低7位数据,status&0x7f(0111 1111)

进程退出返回值获取:4字节中低16位中的高8位(status>>8)&0xff

WIFEXITED(status)等价于(exit_code & 0x7f) == 0正常终止返回true

WEXITSTATUS(status)等价于0xff &(exit_code>>8)

四、替换

概念

程序替换:将一个新的程序加载到内存中,然后改变一个进程的页表映射信息,将其更改到新的程序的指令和数据上,这时候当前的pcb管理的就不是原来的程序运行了,而是一个新的程序。

 代码:

#include<stdio.h>

//程序是有运行参数和运行变量的
//程序的运行参数在赋予的时候,是运行程序的时候以空格间隔在程序后  ./main -l -a -c
//程序的运行参数,就会被保存在argc这个字符数组中
//程序的环境变量,就保存在env这个字符数组中,或者有个全局变量environ

//程序的运行参数,和环境变量都可以从外界传入,这样的话对于程序的运行就会非常灵活,比如ls程序的-l-a
int main(int argc , char *argv[] , char *env[])
{
    extern char **environ;
    
    printf("程序的参数个数:%d\n" , argc);
    printf("程序的运行参数:\n");
    for(int i = 0;argv[i]  != NULL;i++){
        printf("\t%s\n" , argv[i]);
    }

    printf("程序的环境变量:\n");
    for(int i = 0;environ[i]  != NULL;i++){
        printf("\t%s\n" , environ[i]);
    }
    return 0;
}

execl函数簇(了解)

extern char **environ;

int execve(const char *path , char *const argv[] , char *const envp[]);系统调用接口

const char *path:要加载替换的新的程序文件路径名称

char *const argv[]:程序的运行参数

char *const envp[]:进程的环境变量

int execl(const char *path , const char *arg , ....);

path:带路径的新名称,arg是参数,不定参

execl("./example" , "./example" , "-a" , "-l" , NULL);

int execlp(const char *file , const char *arg , ...);

file:不带路径的新名称,在path环境变量指定的文件路径下找程序,常用于指令程序的替换

execlp("ls" , "ls" ,  "-l" , NULL);

int  execlp(const char *path , const char *arg , .... , char *const envp[]);

path:带路径的新名称;arg是参数,不定参;envp是环境变量

execlp("./example" , "./example" , "-a" , "-l" , NULL,environ);

int execv(const char *path , char *const argv[]);

与execl的区别在于参数不是一个一个给,是组织成数组给

int execvp(const char *file, char *const argv[]);

在指定路径下找程序,参数通过数组给

l和v的区别在于参数是一个一个给,还是组织成数组给

有没有p的区别,在于是否会默认到path环境变量指定的路径下找程序

有没有e的区别,在于是否需要自定义变量

实现简单的minishell:

shell:命令行解释器,功能就是捕捉用户输入,然后根据输入的信息指令命令程序

[username@hostname] $ ls -a -l

1.捕捉用户输入

        gest(char *buf); 从标准输入读取一行数据"      ls      -a       -l "

2.字符串解析

        字符串解析,得到命令名称和运行参数"      ls      -a       -l " -> argv[]={"ls"  "-a"  "-l"}

3.创建子进程

        给子进程进行程序替换,并设置运行参数  execvp(argv[0] , argv);

        注意:不能直接对shell替换,因为替换后运行完新程序进程就退出了,如果替换了shell运行完就会退出,并且万一要是某个指令崩溃了,shell就崩溃了,因此要创建子进程让子进程运行指令

4.进程等待

        防止子进程运行完指令退出后成为僵尸进程

5.循环到第一步,重新开始捕捉输入

//minishell代码实现:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值