以下是阿鲤对以下进程控制的总结,希望对大家有所帮助;若有误请慷慨指出。
一:进程创建
二:进程终止
三:进程等待
四:进程替换
五:minshell
注:以下的代码实现均为centos7环境
一:进程创建
进程创建的流程:创建一个pcb -> 复制父进程中的信息
解析来图解程序创建的过程
1:首先假设我们运行了一个test文件 ./test
那么操作系统就会创建一个pcb(进程)然后进程中会有一个指针指向虚拟地址空间,然后通过虚拟地址空间访问到物理地址;如下图。
2:进程创建
进程在复制时会把进程的虚拟地址空间页表一起复制过来,如下图
这样我们会发现子进程所指向的和父进程的内存快是相同的,但是我们知道父子进程之间是是代码共享数据独有的特点,那么这是怎样实现的呢?
这是因为父子进程之间存在一个写时拷贝的机制,即在只改变子/父进程中的某个变量的时,会重新开辟一块内存空间;否则一直父子进程一直使用同一内存;从而提高效率。
3:代码实现
上面的进程创建机制通过pid_t fork()函数进行实现。
eg:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
printf("********%d\n",getpid());
pid_t pid = fork();
if(pid < 0)
{
printf("fork errpr\n");
return -1;
}
else if(pid == 0)
{
//这段代码只能子进程能访问到
printf("this is child--%d\n",getpid());
}
else if(pid > 0)
{
//这段代码只有父进程能够运行到
printf("this is parent--%d\n",getpid());
}
//下面的代码父子进程均可以运行到
while(1)
{
printf("parent and child%d\n",getpid());
sleep(1);
}
printf("W-R\n");
return 0;
}
运行结果:
eg:
然后我们将程序运行起来查看就可以看到以下情况
然后查看进程就会发现出现了父子进程
注意:
1:子进程的创建时通过复制父进程的信息,所以子进程所运行的代码是从成功创建之后运行的;
2:父子进程之间具有代码共享数据独有的特点
3:使用fork()创建子进程fork时会有三种返回值,当返回值等于于0时代表只有子进程可以访问;当返回值大于0时代表只有父进程可以访问;当返回值小于0时代表创建出错;
当然除了fork()之外还有一种创建方式,pod_t vfork()
vfork():调用clone(),复制父进程,但是父子进程共用同一块虚拟地址。
vfork的特点:它是一个阻塞函数(防止调用栈混乱),父进程创建子进程后,这个vfork函数在父进程中不会立即返回,会阻塞直到子进程exit()退出,才会返回;这意味着父进程直到子进程退出或程序替换之后才会运行;所以现在vfork使用率越来越低。
二:进程终止
进程终止即退出一个进程;
而程序退出分为异常退出和正常退出:
异常退出:程序运行过程异常崩溃,程序没有运行完毕
正常退出:程序运行完毕(是否结果符合预期)
在linux下有一个perror函数他可以接收程序所运行的错误并打印错误信息,如下图
在系统调用中,每一次系统调用完成都会重置errno,从而通过errno在错误列表中找到错误原因;
如何终止一个进程?
1:main函数中的return
在我们的程序中mian函数中的return就会退出一个进程(结束程序的运行)
2:exit()(int status)--库函数
首先我们看一下man手册中断exit函数的描述
可以看出来,exit()函数的功能是使得程序即进程正常终止
3:_exit(int status)--系统调用接口
可以看出来,_exit()函数的功能也是使得程序即进程正常终止
那么他们两个有什么区别吗?
区别:exit结束进程时会刷新缓冲区而_exit不会。
eg
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid = fork();
if(pid == 0)
{
printf("haha");
exit(0);//刷新缓冲区 0代表正常退出
}
else if(pid >= 0)
{
printf("hehe");
_exit(0);//不会刷新缓冲区
}
return 0;
}
输出结果:
三:进程等待
功能:等待一个进程的退出,获取退出子进程的返回值,并且释放子进程资源防止僵尸进程的出现。
操作:两个接口
pid_t wait(int *status) pid_t
waitpid(pid_t pid, int *status, int options)
1:pid_t wait(int *status):等待任意一个子进程退出,将返回值放到status中,返回退出子进程的pid;并且是一个阻塞函数
eg:
#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)
{
exit(0);
sleep(5);
}
wait(NULL);
while(1)
{
printf("hello Al\n");
sleep(1);
}
}
2:waitpid(pid_t pid, int *status, int options) :等待指定进程的退出
pid:等待的进程id(-1:等待任意进程); status:用于获取返回值 ; options:阻塞或不阻塞(0:阻塞, WNOHANG:非阻塞);
返回值:-1:出错 ==0表示没有子进程退出 >0表示退出的子进程pid
3:status:获取退出返回值;
在32位系统中,status会取低16位的高8位的为返回值,所以在打印status时需要 >>16&0Xff而;在status的低7位存储异常退出的信号值(0正常退出,非0异常退出);而低8位的高1位是一个core dump标志,用来核心转储错误信息。
所以status可以用来判断程序是否正常退出,在这里直接有一个接口WIFEXITED()来判断(正常退出返回真,异常退出返回假);
注:阻塞和非阻塞
阻塞:为了完成某个功能而发起调用,若当前不具备完成条件则等待,直到条件具备完成功能后返回。
非阻塞:为了完成一个功能,发起调用,若当前不具备完成条件,则立即报错返回;通常需要循环操作。
四:进程替换
我们往往在运行一个程序的时候想要在这个程序中运行一个其他的程序,所以这里就需要用到程序替换;往往是做出一个子进程然后使用其他的程序替换这个子进程。
图解:
首先做出一个子进程;
然后替换这个子进程所映射的地址,换成另一个程序
这样mian这个程序就变成test程序下运行的一个副程序了。
那么这是怎样实现呢?
那就是exec函数簇:以下六个函数
int execl(const char *path, const char *arg, ...)
path:新替换程序的路径; arg:参数信息 ...不定参
int execlp(const char *file, const char *arg, ...)
file:新替换程序的文件; arg:参数信息, ...不定参 (因为直接给文件,所以运行的代码必须在当前路径下)
int execle(const char *path, const char *arg, ..., char *const env[])
与execl相比需要自己传环境变量;(上面两个默认父进程的环境变量)。
int execv(const char*path, char *const argv[]);
和上面三个相比吧参数换成了数组
上图中六个函数均是库函数,且均是封装下面的函数实现的。
int execve(const char*path, char argc[], char *const env[]);
总结:不带p需要路径,带e需要环境变量,带v的以数组方式传参
五:minshell
eg:minshell
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<ctype.h>
#include<sys/wait.h>
int main()
{
while(1)
{
printf("[BelongAL@localhost] ");
fflush(stdout);//刷新标准输出缓冲区
char cmd_buff[1024] = {0};
fgets(cmd_buff, 1023, stdin);//从标准输入读取数据
cmd_buff[strlen(cmd_buff)-1] = '\0';
printf("cmd:[%s]\n",cmd_buff);
char *argv[32] = {NULL};
int argc = 0;
char *ptr = cmd_buff;
while( *ptr != '\0')
{
if(!isspace(*ptr))
{
argv[argc] = ptr;
argc++;
while(!isspace(*ptr) && *ptr != '\0')
{
ptr++;
}
*ptr = '\0';
}
ptr++;
}
argv[argc] = NULL;
pid_t pid = fork();//创建子进程
if(pid < 0)//若创建错误就重新创建
{
continue;
}
else if(pid == 0)//运行程序
{
execvp(argv[0],argv);
exit(0);//若程序替换失败就直接退出(否则子进程就会变成另一个shell,一个终端不需要多个shell)
}
wait(NULL);//进程等待
}
return 0;
}