嵌入式学习的第二十八天-进程2
2)exit(), c库函数,会执行io库的清理工作,关闭所有 的流,以及所有打开的文件。这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往。execv,execvp,execve,需要构造一个参数指针数组,然后将数组的
一、父子进程的关系
子进程是父进程的副本。子进程获得父进程数据段,堆,栈,正文段共享。
在fork之后,一般情况那个会先运行,是不确定的。如果非要确定那个要先运行,需要IPC机制。
区别:
1)fork的返回值
2)pid不同
二、进程的终止(8种情况)和退出
1..进程的终止:8种情况
主动退出
1)main 走到return
2)exit() //库函数
c库函数,会执行io库的清理工作,关闭所有的流,以及所有打开的文件。已经清理函数 (atexit)。【只要有调用该函数,则结束进程】
3)_exit,_Exit 会关闭所有的已经打开的文件,不执行清理函数。
4) 主线程退出
5)主线程调用pthread_exit
异常终止
6)abort() //系统发送重终止信号
不允许应用层调用,若产生严重错误,系统自行调用该函数,关闭进程
7)signal // 发信号 kill pid
8)最后一个线程被pthread_cancle
2.进程的退出
僵尸进程和孤儿进程
僵尸进程:进程执行结束但空间未被回收变成僵尸进程,子进程消亡后pcb块未被回收,父进程还在
孤儿进程:父进程先消亡
三、相关函数

1.exit 库函数 (man 3)
退出状态,终止的进程会通知父进程,自己使如何终止的。
如果是正常结束(终止),则由exit传入的参数。
如果是异常终止,则有内核通知异常终止原因的状态。
任何情况下,父进程都能使用wait,waitpid获得这个状态,以及资源的回收。
#include<stdlib.h>
void exit(int status)
exit(1);
功能:让进程退出,并刷新IO缓存区
参数:status:进程退出的状态
返回值:缺省
EXIT_SUCCESS 0
EXIT_FAILURE 1
return 当该关键字出现在main函数中时候可以结束进程
如果在其他函数中则表示结束该函数。
exit -> 刷新缓存区 -> atexit注册的退出函数 -> _exit
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char **argv)
{
printf("hello");
exit(1);//全面回收工作,文件关闭,堆释放,缓冲区清理
printf("aaaaaa");
//system("pause");
return 0;
}
2._exit 系统调用 (man 2)
#include<unistd.h>
void _exit(int status);
功能:让进程退出,不刷新缓存区,进程立刻结束
参数:status:进程退出状态,值给父进程
返回值:缺省
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(int argc, char **argv)
{
printf("hello");
_exit(1);//只做文件关闭工作
printf("aaaaaa");
//system("pause");
return 0;
}
3.atexit (man 3)
#include<stdlib.h>
int atexit(void (*function)(void));
功能:注册进程退出前清理的函数
参数:function:函数指针;指向void返回值void参数的函数指针
返回值:成功返回0;失败返回非0
当程序调用exit或者由main函数执行return时,所有用atexit;注册的退出函数,将会由注册时顺序倒
序被调用
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
char *p;
void clean(void)
{
printf("this clean fun ,%s\n",p);
}
int main(int argc, char **argv)
{
p=malloc(50);
atexit(clean);
strcpy(p,"hello");
printf("befor exit\n");
exit(1);//如果有注册有清理函数,只能调用exit
//system("pause");
return 0;
}
上述三个函数的综合使用:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
void cleanup1 (void)
{
printf("----------cleanup1-------\n");
}
void cleanup2 (void)
{
printf("----------cleanup2-------\n");
}
void cleanup3 (void)
{
printf("----------cleanup3-------\n");
}
int main(int argc, char **argv)
{
atexit(cleanup1);
int i = 0;
for(i = 0;i < 5;++i)
{
printf("n = %d\n",i);
}
//_exit(0);
exit(0);
atexit(cleanup2);
atexit(cleanup3);
return 0;
}

4.进程空间的回收
exit(20);
wait/waitpid
pid_t wait(int *status);
功能:该函数可以阻塞等待任意子进程退出并回收该进程的状态,一般用于父进程回收子进程状态
参数:status 进程退出时候的状态
如果不关心其退出状态一般用NULL表示;
如果要回收进程退出状态,则用WEXITSTATUS回收。
注:阻塞:进程在运行过程中由于某种条件未到达,导致程序被迫进入阻塞状态
宏:
正常结束:
WEXTSTATUS(wstatus)//判断子进程是否正常结束,正常快结束则为真
WEXTSTATUS(wstatus)//获得exit(status) _exit(ststus) return status
//status&3077

返回值:成功 回收的子进程pid; 失败 -1;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char **argv)
{
pid_t pid=fork();
if(pid>0)
{
printf("father %d\n",getpid());
pid_t ret =wait(NULL);//阻塞(block),父进程需等子进程结束
printf("pid :%d ret %d\n",pid,ret);
}
else if(0==pid)
{
int i = 3;
while(i--)
{
printf("child %d\n",getpid());
sleep(1);
}
}
else
{
perror("fork");
return 1;
}
return 0;
system("pause");
return 0;
}
(1)pid_t wait(int *status);
#include <sys/types.h>
#include <sys/wait.h>
功能:等待子进程状态发生改变;结束、暂停、恢复
参数:status 子进程退出时候的状态信息,如果不关注退出状态用NULL;//被调修改主调
注:
1)如果所有的子进程都在运行,在阻塞
2)如果一个子进程终止,正在等待的父进程则获得终止状态,获得子进程的状态后,立刻返 回。
3)如果没有子进程,则立即出错退出。
(2)pid_t waitpid(pid_t pid, int *status, int options);
waitpid(-1,&status,0)=wait(&status);
功能:等待子进程状态改变

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char **argv)
{
pid_t pid=fork();
if(pid>0)
{
printf("father %d\n",getpid());
int status;
pid_t ret =wait(&status);
if(WIFEXITED( status))//子进程正常结束
{
printf("child terminal normally,exit value %d\n",WEXITSTATUS(status));
}
else if(WIFCONTINUED(status))//异常结束
{
printf("child terminal by signal,signal value %d\n",WTERMSIG(status));
}
printf("pid :%d ret %d\n",pid,ret);
}
else if(0==pid)
{
int i = 10;
while(i--)
{
printf("child %d\n",getpid());
sleep(1);
}
exit(20);
}
else
{
perror("fork");
return 1;
}
return 0;
system("pause");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char **argv)
{
pid_t pid = fork();
if (pid > 0)
{
printf("father %d\n", getpid());
int status;
while (1)
{
pid_t ret = waitpid(pid, &status, WNOHANG);
if (pid == ret)
{
if (WIFEXITED(status)) //子进程正常结束
{
printf("child terminal normally,exit value %d\n",
WEXITSTATUS(status));
}
else if (WIFCONTINUED(status)) //异常结束
{
printf("child terminal by signal,signal value %d\n",
WTERMSIG(status));
}
printf("pid :%d ret %d\n", pid, ret);
break;
}
else if (0 == ret)
{
printf("回收失败,稍后再试\n");
}
else
{
printf("回收错误\n");
return 1;
}
}
}
else if (0 == pid)
{
int i = 3;
while (i--)
{
printf("child %d\n", getpid());
sleep(1);
}
exit(20);
}
else
{
perror("fork");
return 1;
}
return 0;
system("pause");
return 0;
}
练习:
设计一个多进程程序,用waitpid函数指定回收,其中的某个进程资源并将其状态打印输出。其他的
进程都以非阻塞方式进行资源回收。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int i=0;
int n=5;
pid_t pid[5]={0};
for(i=0;i<5;++i)
{
pid[i]=fork();
if(pid[i]>0)
{
continue;
}
else if(0==pid[i])
{
int num =rand()%5+1;
printf("child:%d\n",getpid());
sleep(num);
exit(1);
}
else
{
perror("fork\n");
return 1;
}
}
while(1)
{
pid_t recycle=waitpid(pid[2], NULL, WNOHANG);
if(recycle==pid[2])
{
printf("recycle child pid:%d\n",recycle);
break;
}
else if(0==recycle)
{
}
else
{
perror("waitpid");
return 1;
}
}
return 0;
}
5. execute exec族
功能:用来执行一个新的进程
通过将新进程的各个阶段替换当前进程的各个阶段 实现的

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往
要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和
数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用
exec前后该进程的id并未改变。其实有六种以exec开头的函数,统称exec函数:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
// execl("/bin/ls","ls","-l","-a","--color=auto",NULL);//list
//execl("/usr/bin/firefox","firefox","www.baidu.com",NULL);
// execl("b.out","b.out",NULL);
//printf("看见就错了");
//echo $PATH
//execlp("ls","ls","-l","-a","--color=auto",NULL);//list path
//execlp("cat","cat","./01forck.c",NULL);
// execlp("./b.out","b.out",NULL);
char *args[]={"ls","-l","-a","--color=auto",NULL};
//execv("/bin/ls",args);//vector
execvp(args[0],args);
printf("看见就错了");
return 0;
}

char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};
char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execv("/bin/ps", ps_argv);
execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);
execve("/bin/ps", ps_argv, ps_envp);
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execvp("ps", ps_argv);
这些函数的区别:
1)前4个使用路径名作为参数,后面两个使用文件名做参数
当filename中,含有/时视为路径名,否则就按PATH变量,在指定目录下查找可执行文件。
2)相关的参数表传递
l表示list,v表示vector
execl,execlp,execle,需要将参数一个一个列出,并以NULL结尾。
execv,execvp,execve,需要构造一个参数指针数组,然后将数组的地址传入。
3)以e结尾的函数,可以传入一个指向环境字符串的指针数组的指针。其他未指定环境变量,使用父进程继承过来的。
execve 是真正的系统调用
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。
6.system("vim 1.c");
int system(const char *command); fork+exec
功能:使用该函数可以将shell命令直接在代码中执行。
参数:command要执行的shell命令
返回值:成功 0
失败 -1
system("vim 01fork.c");
四、总结


补充:
1.whereis xx 查看计算机上是否有装载该命令,需是系统级别的,不可查看可执行文件
2.echo 命令行打印 PATH系统级别的环境变量 代表路径
3.ps aux 查看命令路径
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐




所有评论(0)