Linux进程管理

目录

一、进程创建

写时拷贝

二、进程终止

 三、进程等待

四、进程程序替换


一、进程创建

        代码内fork():创建子进程,每个fork只会执行一次

                子进程拥有和父进程相同的代码和数据结构,并且从fork()的下一行开始运行

        fork()的头文件:<unistd.h>

        pid:进程id        ppid:父进程id

printf("A,pid:%d,ppid:%d",getpid(),getppid());        //pid:1,ppid:bash
fork();
printf("B,pid:%d,ppid:%d",getpid(),getppid());        //父进程,pid:1,ppid:bash
//printf("B,pid:%d,ppid:%d",getpid(),getppid());    //子进程,pid:2,ppid:1

fork()之后通常要用if分流,根据返回值区分父进程和子进程

        int ret = fork();  --  返回值:pid_t fork();

                当子进程创建成功,返回子进程的pid给父进程,返回0给子进程

                当创建失败,返回-1给父进程

        fork之后:

                1.执行流会变一为二

                2.谁先运行由调度器决定

                3.代码共享,通常用if else来分流

        原理:

                fork做了什么?

                        进程 = 内核数据结构 + 自己的代码和数据 = pcb + 代码数据

                        在内核中创建子进程的pcb,子进程和父进程的pcb指向同一块代码和数据

                fork如何看待代码和数据?

                        代码:只读

                        数据:当有一个执行流尝试修改数据时,OS会自动给当前进程触发写时拷贝

                如何理解fork有两个返回值?

                        fork本质就是OS提供的一种函数,在return之前子进程已经完成创建,所以子进程也会进行return

int main(){
  pid_t ret = fork();
  if(ret == 0){
    //子进程
    while(1){
      printf("child ,pid: %d ,ppid: %d\n",getpid(),getppid());
      sleep(1);
    }
    
  }else if(ret > 0){
    //父进程
    while(1){
      printf("parent ,pid: %d ,ppid: %d\n",getpid(),getppid());
      sleep(1);
    }
  }else{
    perror("error!\n");
  }
  return 0;
}

写时拷贝

        在Linux环境下,子进程刚创建时会和父进程指向同一块虚拟空间,也是同一块物理空间,当父进程或者子进程对某项数据进行修改时,会对子进程的该数据进行拷贝,复制一份后再进行修改。

        此时虽然父子进程该数据的虚拟地址一样,但是物理地址已经不同,但只要父/子进程没有在子进程创建后对数据进行修改,就不会专门开空间来存放子进程中的数据。

        写时拷贝编译器一种节省空间的手段,赌你不会写入,如果写入才拷贝,如果没有进行写入就赌赢了,就算要进行写入也无非是把本来要做的事情做一遍

二、进程终止

        情况分类:

                1、正常执行结束 -- 正确/不正确

                        返回值可供用户进行进程退出健康状态的判定

                        echo $?:获取最近一次执行的进程的退出码 -- 因为该指令也是一个进程,所以连续的两次echo $?中的第二次获取的是第一次的退出码

                2、崩溃 -- 进程异常

                        崩溃的本质:信号,进程因为某些原因收到了来自操作系统的信号
        

        理解:

                OS内少了一个进程,OS就要释放进程对应的内核数据结构+代码和数据

        操作:

                mian函数用return

                函数用exit(),C语言库函数,等价于main的return

                上述两种退出的流程为:执行用户定义的清理函数 -> 刷新缓冲区,关闭stream -> 终止程序
                _exit()直接终止程序,不会刷新缓冲区

exit()本质:

exit(int code){
    //……
    _exit(code);
}

 三、进程等待

进程等待:通过系统调用,获取子进程退出码或者信号的方式

        用途:

                1、避免内存泄露

                2、获取子进程的执行结果 -- 信号+退出码

        通过wait()、waitpid()获取子进程的退出码 -- 头文件<sys/wait.h>

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

                        pid: > 0 等待指定进程
                                 = -1等待任意进程,与wait等效

                        status:输出型参数,获取子进程的状态

                                status用位图存放,把32个bit位对半切,只用前16位

                                8~15位存放退出码

                                0~6位存放终止信号(0:代表正常退出,正常退出再查退出码,非正常退出就不用退出码)

                                7存放code dump标志

                options: = 0,阻塞等待 -- 只问一次结果,没结果就阻塞,等待结束

                                = WNOHANG,非阻塞等待 -- 过一段时间问一次结果,没结束就干自己的事情

        父进程通过获取子进程的内核数据结构来获取子进程的退出信息

        子进程没有退出时,父进程只能一直在调用waitpid进行等待 -- 阻塞

int main(){
  pid_t id = fork();
  if(id == 0){
    //子进程
    int cnt = 5;
    while(cnt){
      cout << "child "<<"pid:"<<getpid()<<" ppid:"<<getppid()<< " 剩余时间:" <<cnt-- << endl;
      sleep(1);
    }
    exit(0);
  }
  //父进程
  //pid_t ret = wait(NULL);
  
  //父进程开始时睡眠10s,意味着当子进程结束后其退出码还有5s没被父进程获取,此时子进程为僵尸状态
  sleep(10);
  int status = 0;
  pid_t ret = waitpid(id,&status,0);
  cout <<"parent "<<"pid:"<<getpid()<<" ppid:"<<getppid()<<" ret:"<<ret<<" status:"<<status<<" 退出状态:"<<((status<<8)&0xFF)<<" 终止信号:"<<(status&0x7F)<<endl;
  return 0;
}

四、进程程序替换

        创建子进程的目的:

                1、让子进程执行父进程的一部分代码

                 2、执行一个磁盘中已有的进程 -- 进程的程序替换

        原理:

                没有创建新的进程,只替换数据和代码,不替换PCB

                原代码被新代码替换后,剩余的代码已经没了,就不会再执行

                如果没有成功替换,则继续执行源代码

        操作:

                1、找到一个程序

                2、加载执行 -- 语法和在命令行上执行的语法一样

                3、以NULL结尾

//		路径(要找到该进程)	可执行程序	可变参数
int execl(const char *path, const char *arg, ...);
//如果替换成功就不会有返回值,只要有返回值就代表替换失败 -- 不用判断该函数的返回值,只要继续向后执行就代表失败		
int main(){
    pid_t id = fork();
	if(id == 0){
    	cout << "child:" <<getpid()<<endl;
	    成功
	    //execl("/bin/ls", "ls", "-a", "-l",NULL);
	    //失败
	    execl("/bin/ls1111111","ls","-a","-l",NULL);
		cout <<"child fail"<<endl;
		exit(1);
	}else{
		sleep(5);
		int status = 0;
		cout << "parent:"<<getpid()<<endl;
		waitpid(id,&status,0);
		cout << "child exit code:"<<WEXITSTATUS(status)<<endl;
	}
	 return 0;
}

        其他接口:

//		  路径(要找到该进程)	可执行程序	可变参数
//int execl(const char *path, const char *arg, ...);
//调用自己写的程序:
execl("./other/othertest", "othertest", NULL);

//v:vector	 路径			    二维数组
//int execv(const char *path, char *const argv[]);
char *const myargv[]={"ls", "-a", "-l", NULL};
execv("/bin/ls", myargv);

//p:PATH -- 在环境变量中查找,需要先在环境变量中设置
//			环境变量名-进程名
//int execlp(const char *file, const char *arg, ...);
execlp("ls", "ls", "-a", "-l", NULL);
int execvp(const char *file, char *const argv[]);
char *const myargv[]={"ls", "-a", "-l", NULL};
execvp("ls", myargv);

//												    环境变量(会覆盖系统的环境变量)
//int execle(const char *path, const char *arg,..., char * const envp[]);
const char* myenv[]={"MYENV=thismyenv", NULL};
execle("./other/othertest", "othertest", NULL, myenv);
//不传自定义的环境变量,传父进程的环境变量:
//获取环境变量
extern char** environ;
execle("./other/othertest", "othertest", NULL, environ);
//在当前进程中新增环境变量:int putenv(char *string);
//添加环境变量
putenv("MYENV=NEWENV");
execle("./other/othertest", "othertest", NULL, environ);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值