Linux下进程控制

进程创建

创建一个进程的流程 : 创建一个pcb,复制父进程中的信息。
Linux创建进程有两个函数
1. pid_t fork()
返回值: 父进程返回子进程的pid,子进程返回0,创建错误返回-1
父进程创建子进程后,代码共享,数据独有 (利用了写实拷贝技术,)
用fork()函数建立的子进程几乎与父进程完全一样,子进程中的所有变量均保持他们在父进程中的值(当然fork的返回值除外),因为自己称可用的数据是父进程可用数据的拷贝,并且其占用不同的内存地址空间(当然逻辑地址可能是一样的),这就保证了在一个进程中的变量数据变化不会影响到另外一个进程。这一点非常重要。
但是有一点要特别注意的,在父进程打开的文件,那么在子进程中也会打开,继承了打开的文件描述符,但是子进程与父进程共享文件表,所以有一个进程中的文件偏移量发生变化就会直接影响到另外一个进程

例如 ,我们在进程中创建了一个a.txt的文件,然后创建子进程,通过复制过来的文件描述符来向文件里写数据,随后我们在父进程访问a.txt 文件,能够访出刚才子进程写入的数据。

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<stdlib.h>
int main(){
        int a=10;
        int fd= open("a.txt",O_RDWR|O_CREAT,0664);
        pid_t pid=fork();
        if(pid==-1){
                perror("create error\n");
                exit(-1);
        }
        else if(pid==0){

        write(fd,"123456789",9);
        close(fd);
        exit(0);
        }
        else{
                sleep(5);
                lseek(fd,0,SEEK_SET);
                char buf[256]={0};
                read(fd,buf,256);
                printf("%s",buf);
                close(fd);
        }
        return 0;
}

在这里插入图片描述

2. pid_t vfork—创建一个子进程
调用clone,创建pcb,复制父进程,但是子进程与父进程共用同一个虚拟地址空间。
vfork是一个阻塞函数,父进程创建子进程之后,这个vfork函数在父进程中并不会立即返回,会阻塞直到子进程exit退出,或则程序替换,才会返回(意味着父进程直到子进程退出或则程序替换之后才会运行) 但是vfork已经被淘汰掉了,因为vfork的存在意义就是创建子进程的效率更高,但是fork实通过写时拷贝技术实现了进程创建之后,被淘汰了

防止调用栈混乱—因为共用虚拟地址空间。

进程终止

进程退出分为两种情况:
正常退出:结果符合预期、结果不符合预期
异常退出: 常见的程序崩溃。

如何终止一个进程:
main函数中的return,
会刷新缓冲区,
exit(int status) —库函数
任意位置调用都是退出进程
并且退出前刷新缓冲区 只会释放pcb
** void _exit(int status)** ----系统调用接口
不刷新缓冲区。缓冲区的数据被丢弃,任意位置调用都是退出进程
库函数和系统调用接口关系:上下级的封装关系。

进程等待

等待子进程的退出,获取退出子进程的返回值,并且释放子进程资源,防止出现僵尸进程。
进程等待原因
因为子进程退出时为了保存退出原因,因此操作系统不能释放子进程全部资源,因此通知父进程获取子进程退出返回值,允许释放资源,但是通常这个操作的通知是静音的,导致父进程没有关注到子进程的退出,因此子进程成为僵尸进程。若父进程获取了子进程的返回值,僵尸子进程将没有存在的意义,就会被释放资源。因为不知道子进程何时退出,因此只能创建之后一直等着子进程退出。

如何等待
有两个等待函数。
*pid_t wait(int status);
这是一个阻塞函数,等待任意一个子进程退出,将返回值放到status中,返回退出子进程的pid。如果一直没有子进程退出,wait函数将一直阻塞。
阻塞
为了完成一个功能发起调用,当前若不具备完成条件,等待直到条件具备完成功能后返回

非阻塞:
为了完成一个功能发起调用,当前若不具备完成条件,则立即报错返回。
阻塞与非阻塞的区别:调用是否立即返回。
(通常非阻塞通常需要用循环来解决)
*pid_t waitpid(pid_t pid,int status,int opions);
等待指定子进程/任意一个子进程退出.,返回值放到status中,
pid=-1 :等待任意一个子进程, pid>0:等待指定子进程。
options为0时,则函数默认阻塞,option 若为WNOHANG,则将waitpid设置为非阻塞,意味则没有子进程退出则立即返回。

返回值 : -1表示出错 ==0 表示没有子进程退出, >0表示退出子进程的pid

而status是用于获取进程退出时的返回值,statu中,高16位没有使用,其中低16位中,高8位存储子进程退出返回值,低8位中高一位中存储core dump标志, 低七位中存储异常退出信号值。低7位中异常退出信号值若为0,则表示程序正常退出。否则程序异常退出,退出的返回值不具备判断意义

获取低7位 : statu & 0x7f
获取低16位中的高8位 ( statu>>8 ) & 0xff
下面这段代码先创建一个子进程,然后子进程随眠5秒,与之同时,父进程的waitpid函数采用的是非阻塞方式,等子进程退出后,ret=pid ,这时退出while循环。这样就不会产生僵尸进程了

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

int main(){
        pid_t pid=fork();
        if(pid<0){
                //打印上一个系统调用错误信息
                perror("fork error");
                exit(-1);
        }
        else if(pid==0){
                sleep(5);
                exit(0);
        }
        else{
                //wait(NULL);
                int ret;
                while((ret=waitpid(pid,NULL,WNOHANG))==0){
                        printf("过一会,再来看一眼\n");
                        sleep(1);
                }
                if(ret<0){
                        perror("waitpid error");
                }
                else if(ret==0){
                        printf("have no child exit\n");
                }
                else{
                        printf("pid:%d child exited\n",ret);
                }
                while(1){
                        printf("打麻将~~~开心\n");
                        sleep(1);
                }
          }
         return 0;
}

在这里插入图片描述
WIFEXITED(statu) 判断是个进程是否正常退出

程序替换

替换一个进程正在运行的程序,
就是替换一个pcb映射在内存中的代码和数据,说白了就是让这个进程运行另外一个程序,重新加载一个新的程序到物理内存中,对一个进程的代码段通过页表在物理内存中的地址进行一个修改,修改映射关系,让程序的代码段经过页表转换后,指向新的程序位置。 让一个进程pcb通过页表转换映射到物理内存上另一个程序地址;进程将运行另一个程序,以前的代码和数据都失效了,因此需要重新初始化页表以及虚拟地址空间中的代码段,数据段。
如何进行程序替换: exec函数族
int execl(const char * path,const char * argv,…)
使用path这个路径的程序,替换当前程序要运行的程序。让当前进程运行ls这个程序的功能 后边的arg 以及 。。。 都是这个程序的运行参数

**extern char **environ;
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 execvpe(const char *file, char const argv[], char const envp[]);

上面的都是封装了下面的这个。
int execve(const char * path ,char *const argv[] ,char *const env[]);
加载的程序是否需要确定的给出所在

带p和不带p的区别:
加载的程序名称是否带路径,带p则不需要,只要文件名就行,但是文件必须放在指定路径下PATH
带e与不带e的区别: 要运行的程序是否需要从新自定义环境变量。

v和l的区别: 程序的运行参数是函数的参数平铺,或者直接组织成为字符串指针数组 给与

利用进程替换实现minishell 自主实现minishell

自主实现minishell

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值