进程的创建
进程的控制的前提就是我们必须有一个进程,在上一篇中,我们已经能使用fork创建一个子进程,而它的特点是
1.以父进程为模块复制创建出一个进程,父子进程代码共享。数据独有
2.fork的返回值,父进程返回子进程的pid,子进程返回0
3.fork取决于cpu的调度
而vfork()也是创建一个进程
实现:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int pid=vfork();
if(pid==0)
{
printf("this is child!\n");
exit(0);
}
else if(pid>0){
while(1){
printf("this is parent!\n");
sleep(1);
}
}
return 0;
}
vfork的特性:
1.子进程没有退出或者运行其他程序父进程是阻塞的,也就意味着子进程现运行,子进程退出之后父进程才能运行
2.创建出子进程大多都是为了运行其他程序,所以子进程会先运行
3.由于父子进程公用同一块虚拟地址空间,所以子进程不退出,父进程就会阻塞。
vfork创建子进程的目的就是为了创建一个子进程然后实现其他的程序,重新运行其他程序就是给子进程开辟新的空间,更新它的一份地址空间,但是自从fork函数使用了写时拷贝技术,vfork便被淘汰。
进程终止
进程终止的方式有三种:
1.main函数中的return
2.exit库函数调用
3._exit库函数调用
我们先使用exit库函数来终止一个进程
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<error.h>
#include<string.h>
int errno;
int main()
{
int i;
for(i=0;i<255;i++)
{
printf("%s\n",strerror(i));
}
printf("%s\n",strerror(errno));
perror("asdasdas");
printf("hello world");
exit(0);
sleep(3);
return 0;
}
成功的打印出hello world
我们使用_exit终止一个进程
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<error.h>
#include<string.h>
int errno;
int main()
{
int i;
for(i=0;i<255;i++)
{
printf("%s\n",strerror(i));
}
printf("%s\n",strerror(errno));
perror("asdasdas");
printf("hello world");
_exit(0);
sleep(3);
return 0;
}
并没有打印出hello world
exit和_exit的区别
exit是温和型的退出,在退出前会温和的释放资源,刷新缓冲区
_exit是暴力型的退出,直接释放资源,不刷新缓冲区
但是不管是哪一种主动退出的方式都会返回一个数字,这个数字就是进程的退出状态,它表明了进程退出的原因
1:正常退出完毕,结果符合预期
2:正常运行完毕,结果不符合预期
3:异常退出,返回状态将不作为并判标准
进程等待(重点)
一个进程退出之后,因为要保存自己的退出原因,因此并不会释放所有的资源,它等着父进程查看它的退出原因,然后释放资源,假如父进程根本没有管,那么这个子进程就会变成僵尸进程,造成资源泄漏。
因此为了防止出现僵尸进程,父进程就应该管一下子进程的退出
1.pid_t wait(int status)
参数:
status用于获取子进程退出状态码
返回值是返回退出的子进程的pid
wait函数目的就是为了等待任意一个子进程的退出,应为wait是一个阻塞型的函数,因此如果没有子进程退出那么它就一直等待,直到有子进程退出
int main()
{
pid_t pid=fork();
if(pid<0)
{
perror("fork error");
return -1;
}
else if(pid==0)
{
sleep(3);
exit(0);
}
pid_t id=-1;
if(id=wait(NULL)<0)
{
perror("wait error");
}
printf("child:%d exit:%d\n",od,pid);
return 0;
}
2.pid_t waitpid(pid_t pid, int *status, int options);
三个参数:
1.pid -1 等待任意子进程,>0等待指定的子进程
2.status :获取退出状态码
3.options: 0 阻塞 ; WNOHANG feizuse
返回值 -
int main()
{
pid_t pid=fork();
if(pid<0)
{
perror("fork error");
return -1;
}
else if(pid==0)
{
sleep(3);
exit(0);
}
pid_t id=-1;
while((id=waitpid(pid,NULL,WNOHANG))==0)
{
printf("CSDN");
}
printf("child:%d exit:%d\n",id,pid);
return 0;
}
1,出错;==0,没有子进程退出 ; >0,退出的子进程pid
阻塞:为了完成一个功能而发起的一个函数调用,如果没有完成这个功能则一直挂起等待功能完成才返回。
非阻塞:为了完成一个功能而发起的一个函数调用,如果现在不具备完成的条件则立刻返回不等待
waitpid可以等待指定的子进程,也可以等待任意的子进程。取决于waitpid的第一个参数,
wait是一个阻塞等待式的函数,必须等待到有一个子进程退出后,获取退出状态,释放支援才返回
waitpid可以设置为非阻塞。
一个小型的shell
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
while(1)
{
printf("minishell:");
fflush(stdout);
char buf[1024]={0};
//^\n:scanf本身是遇到空格就要获取一次,这样的话就无法获取
// 遇到一个完整的命令,因此%[^\n]表示的是获取数据知道遇到\n
//%*c:将缓冲区的字符都取出来,但是不要它,直接丢掉
// 目的是为了将最后的\n从缓冲区取出来,防止陷入死循环
//
if(scanf("%[^\n]%*c",buf)!=1)
{
getchar();
}
//将获取到的命令解析一下,然后创建子进程进行程序替换
char* ptr=buf;
char* argv[32]={NULL};
int argc=0;
argv[argc++]=ptr;
i
{
if(isspace(*ptr))//用于判断一个字符是否是:\t \n \r 空格
//解析一个字符串的时候,这里就是对空格的判断
{
while(isspace(*ptr)&&*ptr!='\0')
{
*ptr++='\0';
}
argv[argc++]=ptr;
}
ptr++;
}
if(fork()==0)
{
execvp(argv[0],argv);
}
//需要等待的原因:
//1.避免产生僵尸进程
//2.是为了等待子进程运行完毕,让程序逻辑更加完善
wait(NULL);
}
return 0;
}
对shell的调试结果:
调研popen/system, 理解这两个函数和fork的区别