1.进程创建
2.进程终止
3.进程等待
4.进程替换
1.进程创建
fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include<unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1。
注意:
在进程pcb结构体,有一个结mm_struct ,mm_struct是地址空间的具体结构;task_strasct 是描述pcb属性的结构体。 每一个进程运行之后,都会有自己的进程地址空间和页表映射交过。并且其的pcb会有一个指针指向地址空间,地址空间也有一个指针指向页表。
1.子进程在被父进程创建时会把页表中的数据( 虚拟地址、物理地址、访问权限字段、一个区域用于检查是否有内容和在磁盘是否分配有地址用来判断程序是否被挂起等)一起拷贝但是当子进程要修改内容时,则会另开一物理地址,并修改页表的映射关系使得我们看见虚拟地址没有被修改但内容变了。
2.进程的结构为此复杂实为以统一的视角看待内存,以地址空间+页表可以将乱序的内存数据变得有序。而虚拟地扯空间的存在,可以有效地进行访问内存的安全检查。
3.进程进行各种转换、访问,代表着这个进程正在运行。
4.通过页表,让进程映射到不同的物理内存,实现进程的独立性。
5.父进程创建子进程时首先将自己的读写权限改为只读,然后创建子进程。但当用户写数据写入时, 用户不知权限被修改,导致出错使操作系统介入重新申请内存。
用法:
一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子 进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
2.进程终止
进程退出有三种情况:代码运行完毕,结果正确 ;代码运行完毕,结果不正确 ;代码异常终止。
退出方法:正常:1. 从main返回 ;2. 调用exit ;3. _exit 。 异常:ctrl + c,信号终止。
_exit函数
#include<unistd.h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值
exit函数
#include<unistd.h>
void exit(int status);
exit是库函数,终止进程时会自动刷新缓冲区;_exit是系统调用,终止进程时不会自动刷新缓冲区。
#include <unistd.h>
void exit(int status);
int main()
{
printf("hello");
exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
hello[root@localhost linux]#
int main()
{
printf("hello");
_exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
[root@localhost linux]#
3.进程等待
通过wait人waitpid的方式,让父进程对于进程进行资源回收的等待过程。
目的:
1.解决子进程僵尸状态带来的内存泄漏问题。
2.通过进程等待,获取子进程退出的信息。
wait:回收子进程僵尸状态(z->x)。若子进程没有退出,父进程必须在wait上进行阻塞等待, 直到子进程僵尸,wait回收。
waitpid:获取退出信息。
用法:
pidt wait (int*status) / waitpid(pid_t pid, int*status int option)
pid=-1:任一子进程。 pid>0进程工口相同的子进程。
status是一个int的整数,32bit,低16位:正常终止,15~8 位表示退出状态;被信号所杀8~7表示core dum, 7~0位表示终止信号。
option=O/WNOHANG:阻塞/非阻塞等待。(阻塞等待只能做子进程的事,父进程什么都做不了;非阻塞等待,父进程可以做别的事)
返回值:若没有设置WNOHANG,成功都会返回子进程的pid,失败返回-1;若设有WNOHANG,没有已退出的子进程返回0。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
printf("%s fork error\n",__FUNCTION__);
return 1;
}else if( pid == 0 ){ //child
printf("child is run, pid is : %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);
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;
}
4.进程替换
目的:
1、我们所创建的子进程、执行的代码,都是父进程的一部分。
若要让子进程执行全新的代码和访问全新的数据,叫做程序替换。即当进程调用一种exe函数,该进程的用户空间代码和数据被完全替代,但调用exce 不创建新进程,且该进程id不变。
方法:
替换函数:
调用成功会加载新的程序从启动代处执行,失败返回-1。
int execd(const char*path,const char*arg,.....)
path:要皆换的文件,程序路径名和文件名。 arg:执行操作,类似于命令行操作。
例:execl ("/user/bin/ls","ls","-a","-l",NULL);
表示要自己找到目标可执行程序,但excelp会白动去环境变量去找。
int execlp(const char*file ,const char*arg,....)
file:执行目标。
例:execlp ("ls","ls","-a","-l",NULL);
int execv(const char *path, char *const argv[]);
例:char*const argv[]={"ls","ls","-a","-l",NULL};
execv("/user/bin/ls",argv);
int execvp(const char *file, char *const argv[]);
例:char*const argv[]={"ls","ls","-a","-l",NULL};
execlpargv[0],argv);
int execle(const char *path, const char *arg, ...,char *const envp[]);
envp[]:覆盖式传递环境变量。
例:extern cahr*environ;
int main(){ char*env_val="MYVAL=555"; execle("./mytest","mytest","-a","-",NULL,environ);}
int execve(const char *path, char *const argv[], char *const envp[]);
系统调用接口,加载一个新程序,设置新的运行参数和新的环境变量。
总结:有e则表示需要手动设置环境变量,否则使用默认已有的环境变量;有p表示适用于加载指令的场景,默认可以不要路径,程序会到PATH环境变量下找程序;有v则表示运行参数要先组织成为一个指针数组进行传递;有l表示运行参数逐个通过函数参数赋予,以NULL结尾。