本文主要是介绍Linux7:多进程初步:进程描述,创建进程,特殊的进程,进程资源清理,进程退出,exec函数族以及system函数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Linux7:多进程初步:进程相关概念,创建进程,特殊的进程,进程资源清理,进程退出,exec函数族以及system函数
1.进程描述:
进程初探:
程序
可执行的二进制代码文件
进程
程序被加载到内存中运行
系统中基本的执行单元
联系
具有一定独立功能的程序的一次运行活动,操作系统动态执行的单元,包含程序从调度到消亡的整个过程是动态的过程
运行着的程序都是一个进程
系统本身也运行着许多管理系统资源和用户访问的程序
一个程序可以被加载多次成为不同的进程
查看进程:
windows操作系统下:
任务管理器:
Linux 操作系统下:
shell命令:
ps命令:ps -efUID PID PPID C STIME TTY TIME CMD
用户ID 进程ID号 父进程ID CPU占用率 开始时间 启动的终端 占用CPU总时间 启动命令ps -aux//查看进程的详细信息statS:睡眠R:运行执行或即将运行状态D:不可中断的睡眠(等待)通常等待输入或输出的完成T:停止通常是被shell或调试器控制停止N:低优先级任务nice值被修改Z僵尸进程X死进程s:进程是会话期首进程+:后台进程和用户交互的任务l:进程是多线程的<:高优先级任务
进程号:
进程的标识号(pid) 无符号整形 唯一
内核限制进程号小于等于32767,达到时重置进程号计数器
计数器重置从300开始,1-300被系统进程和守护进程占用
32位开发平台最大是32767,64位达2的22次方
/proc/sys/kernel/pid_max
进程状态:
执行态:该进程正在运行,即进程正在占用 CPU, 任何时候都只有一个进程。
就绪态:进程已经具备执行的一切条件,正在等待分配 CPU 的处理时间片。
等待态:进程正在等待某些事件,当前不能分配时间片, 进程不能使用 CPU,若等待事件发生(等待的资源分配到)则可将其唤醒,变成就绪态
相关概念:
父进程
创建/启动一个进程的进程称之为该进程的父进程
子进程
相对于改程序的父进程,该进程为子进程
父子进程是相对的概念
内存布局:
每一个进程有独立的空间
文本段
包含进程运行的程序机器语言指令
只读性
防止进程通过错误指针修改自身的指令
数据段
初始化数据段
包含显示初始化的全局变量和静态变量
程序加载到内存的时候读取这部分变量
未初始化数据段(BSS段)
未进行初始化的全局变量和静态变量
程序启动之前,系统将本段所有的内存初始化为0
堆
动态开辟的内存
栈
动态增长和收缩的段,由栈帧组成,存放局部变量、函数参数值等
如图:
size命令 //查看进程所占资源
任务调度:
按一定的算法,从一组待运行的进程中选出一个进程占用CPU
进程特点:
动态性
并发性
独立性
异步性
杀死进程:
ps //查看进程
kill -9 进程ID //杀死进程
2.进程的使用:
子进程:
用户进程创建子进程,子进程存在于系统,独立于父进程
可被系统调度,可被分配系统资源
查看进程号:
getpid:
功能
获得进程id
函数原型
pid _t getpid(void)
所属头文件
#include <sys/types.h>
#include <unistd.h>
参数
无
返回值
调用该函数的进程id
getppid:
功能
获得父进程id
函数原型
pid_t getppid(void)
所属头文件
#include <sys/types.h>
#include <unistd.h>
参数
无
返回值
调用该函数的进程的父进程id
创建进程:
fork函数:
功能:
创建新进程
原型
pid_t fork(void)
所属头文件
<unistd.h>
参数
无
返回值
在父进程中返回子进程的PID,在子进程中返回0,失败返回-1
特点
fork成功后,会创建一个子进程,子进程会复制父进程资源父子进程同时从fork函数以下开始并行运行。互不干扰。拥有独立的数据段、堆栈,但无法确定父子进程的运行顺序
代码演示demo2.c:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid=getpid();
fork();//创建一个进程,两个进程在运行,返回两个pid
if(pid==getpid())
{printf("fujincheng\n");
}else
{printf("zijincheng\n");
}
printf("my pid :%d\n当前pid:%d\n",pid,getpid());
return 0;
}
运行结果:
vfork函数:
功能
创建子进程,并且阻塞父进程
原型
pid_t vfork(void)
所属头文件
<unistd.h> <sys/type.h>
参数
无
返回值
在父进程中返回子进程的PID,在子进程中返回0,失败返回-1
特点
vfork成功后,会创建一个子进程,子进程共用(独占)父进程资源,子进程退出父进程才会得到执行。分享父进程的数据段、堆,一定是子进程先运行
注意:
使用vfork的时候,为了保证子进程比父进程先运行,只有子进程运
行exec或者exit函数之后,才会去运行父进程,否则会发生段错误
代码演示:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{int cnt=0;pid_t pid;pid=vfork();//创建子进程if(pid>0){while(1){printf("fujincheng\n");sleep(1);}}else if(pid==0){while(1){printf("ZIjincheng\n");sleep(1);cnt++;if(cnt==3){exit(0);}}}return 0;
}
代码实现:当子进程中cnt==3时,退出,再去执行父进程
运行结果:
特殊进程:
0号进程:
操作系统的引导程序
祖先进程:
操作系统启动的第一个程序,1号进程
孤儿进程:
父进程先退出,子进程被init接管,子进程退出后init会回收其占用的相关资源
在Ubuntu的图形界面下,孤儿进程会被upstart收养而不是init
upstart是Ubuntu使用的用来代替init的,可更快的启动系统,以及在硬件热拔插的时候启动或者停止相关服务
ps -x |grep +僵尸进程的进程号
缺点
子进程的相关资源无法清理回收
僵尸进程:
子进程退出,父进程没有做清理工作
一种非常特殊的进程,它几乎已经放弃了所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间
父进程退出会清理子进程
进程等待:
wait以及waitpid会等待指定的子进程退出然后做清理工作
wait函数
功能
等待调用它的进程,直到子进程结束
函数原型
pid_t wait(int *status);
所属头文件
<sys/types.h><sys/wait.h>
参数
status 若为空,则代表任意状态结束的子进程
status 若不为空,则代表指定状态结束的子进程
一般默认为NULL
返回值
成功返回终止的那个子进程的id,失败返回-1
wait代码演示:
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>#include <stdlib.h>
#include <stdio.h>
int main()
{
pid_t pid,pc;
int status;
printf("wait实例:\n");
pid=fork();
if(pid<0)//创建出错,
printf("error ocurred!\n");
else if(pid == 0) //如果是子进程
{
printf("我是子进程的ID=%d\n",getpid());sleep(10); //睡眠10秒
exit(7);
}
else //父进程
{
pc=wait(&status); //等待子进程结束; 得到子进程的ID
if(WIFEXITED(status)) //子程序正常结束返回非0值,异常返回0
{
printf("我是父进程,我等待的子进程的id号=%d\n",pc);
printf("退出码是%d\n",WEXITSTATUS(status));
}
else
{
printf("子进程退出异常!\n");
}
}
exit(0);
}
代码实现:父进程等待子进程退出。
在第3行结果打印出来前有10 秒钟的等待时间,这就是我们设定的让子进程睡眠的时间,只有子进程从睡眠中苏醒过来,它才能正常退出,也就才能被父进程捕捉到
运行结果:
waitpid函数
功能
暂时停止目前进程的执行,直到有信号来到或子进程结束
原型
pid_t waitpid(pid_t pid,int * status,int options);
所属头文件
#include<sys/types.h>
#include<sys/wait.h>
参数
status:保存进程退出时状态
pid
0:等待指定的pid
-1:等待任意的PID
options
0:同 wait,阻塞父进程,等待子进程退出–一般为0
返回值
成功返回子进程识别码(PID) ,如果有错误发生则返回返回值-1
waitpid(pid,NULL,0);//指定的进程退出
waitpid(-1,NULL,0)//同wait(NULL);
waitpid代码演示:
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>#include <stdlib.h>
#include <stdio.h>
int main()
{
pid_t pid,pc;pid=fork();
int status;
if(pid<0)
{
printf("创建进程失败!\n");
}
else if(pid==0)
{
printf("我是子进程,我的ID=%d\n",getpid());sleep(10);
exit(0);
}
else
{
do
{
pc=waitpid(pid,&status,WNOHANG);//使用了WNOHANG参数,waitpid就不会等待,直接返回0.
// pc=waitpid(pid,&status,0);
if(pc==0)
{
printf("没有收集到子进程!\n");
sleep(1);
}
}while(pc==0);//等不到,继续等,
if(pid==pc)
printf("等到了子进程\n");else printf("出错了\n");printf("我是父进程,我要等的进程id是%d\n",pc);
}
exit(0);
}
运行结果:
从结果看出:
先执行父进程,输出"没有收集到子进程",然后sleep(1),此时执行了子进程,输出"我是…31842",sleep(10);然后执行父进程,因为父进程不会等待子进程,所以不断的收集子进程,直到子进程sleep(10),结束后,才会收集到.
进程退出:
方法:
exit();//正常结束一个进程会清理缓冲区
0 表示正常结束;其他值表示错误,进程非正常结束
函数的参数可在shell中查看
_exit();直接进入内核释放用户进程的地址空间
exit 和_exit 函数都是用来终止进程的。当程序执行到 exit 或_exit 时,进程会无条件地停止剩下的所有操作,清除包数据结构,并终止本进程的运行。
return //自然返回也会结束进程
return语句会被编译器翻译为调用exit
信号
ctrl + c
ctrl +
kill -9 进程号
//杀死指定进程
退出清理函数:
函数原型
int atexit(void (*function)(void));
函数功能
注册退出清理函数,在任意进程结束的时候,自动执
行退出清理函数
函数参数
void (*function)(void)
函数指针,指向一个无参数,无返回值的函数
函数返回值
成功返回0,失败返回非0值
注意:该函数不需要被调用,只要在函数
开始之前进行注册,在进程结束的时候就会自己执行
参考代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int i=0;
void clean(void)
{
i++;
printf("i=%d\n",i);
}
int main()
{
atexit(clean);
fork();
fork();
}
运行结果:
atexit:
是在进程结束的时候自动执行清理函数。
3.执行程序:
常用的函数族:
exec 函数族就提供了一个在进程中启动另一个程序执行
#include <unistd.h>
extern char **environ;int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
使用原因:
当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何 exec 函数族让自己重生
如果一个进程想执行另一个程序,那么它就可以调用 fork 函数新建一个进程,然后调用任何一个 exec,这样看起来就好像通过执行应用程序而产生了一个新进程。
exec函数族:
int execl(const char *path, const char *arg, …);逐个列举的方式
int execlp(const char *file, const char *arg, …);从PATH 环境变量中查找文件并执行
int execle(const char *path, const char *arg,…, char * const envp[]);
int execv(const char *path, char *const argv[]);将所有参数整体构造指针数组传递
int execvp(const char *file, char *const argv[]);
l->list
命令参数以列表的方式提供,NULL结尾
v->vector
命令参数以二维数组形式提供,数组的每一行为一个命令行参数
e->environment
传递给程序的环境列表
execl
功能
使用完整的文件目录来查找对应的可执行文件。注意目录必须以“/”开头,否则将其视为文件名
原型
int execl(const char *path, const char *arg, …);
所属头文件
#include <unistd.h>
参数
path:执行文件的路径
arg:可执行文件所需要的参数
参数要以NULL 结尾 (char *)0
返回值
无
特点
当调用execl函数的时候,程序的代码段发生变化,变为execl要执行的功能的代码段,必须以 NULL 表示结束,如果使用逐个列举方式,那么要把它强制转化成一个字符指针
system:
运行shell指令系统函数正常函数调用
参考代码:execl和system函数的比较:
演示代码:
/*******************************
*文件名称: stack.c
*创建时间:20.10.10
*修改时间:20.10.10
*文件功能:顺序栈
*文件版本:v1.0
********************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define MAX 7
enum result
{ERR=-1,OK
};
struct stack
{int data[MAX];int top;
};
int push(struct stack *p,int num);
int pop(struct stack *p,int *d);
int main()
{struct stack s;s.top = -1;int i = 0,num;for(i=0;i<9;i++){if(push(&s,i)==-1)printf("%d\t栈满,无法继续添加数据\n",i);} printf("~~~~~~~~~~~~~\n");//execl("/bin/ls","ls","-l",NULL);//覆盖原程序,进行执行system("ls -l");//执行终端指令 ---执行完,继续原程序for(i=0;i<9;i++){if(pop(&s,&num) ==0)printf("%d 出栈\n",num);elseprintf("栈空\n");}return 0;
}
execl和fork函数的搭配使用:
演示代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{int num = 10;pid_t pid = fork();if(pid < 0){perror("fork");return -1;}else if(pid == 0){printf("child process\n");execl("/bin/ls","ls","-l",NULL);}else{wait(NULL);printf("parent process\n"); }return 0;
}
运行结果:
execl和vfork函数的搭配使用:
vfork父子进程资源共享:在调用exit,exec函数族之前和父进程共享资源。
补充:子进程结束必须要exit或者函数族,如果不使用,运行时会出现段错误。
演示代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{int num = 10;pid_t pid = vfork();if(pid < 0){perror("vfork");return -1;}else if(pid == 0){num++;printf("child process\n");execl("./w","./w","/home/li/text","./89",NULL);exit(0);}else{printf("parent: num=%d\n",num);printf("parent process\n");}return 0;
}
运行结果:和fork一样
execlp:
execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。如果用常数0来表示一个空指针,则必须将它强制转换为一个字符指针,否则将它解释为整形参数
原型
int execlp(const char * file,const char * arg,…,(char *)0);
演示代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{ //execl("/bin/ls","ls","-l",NULL);execlp("ls","ls","-l",NULL);return 0;
}
运行结果:
execle:
可以传递一个指向环境字符串指针数组的指针。
例如:
char *env_init[] = {“AA=aa”,”BB=bb”,NULL};
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{ char *envp[]={"AA=HELLO","BB=123",NULL};//sizeof(envp)=12;shuzuzhizhen(4)//execl("/bin/ls","ls","-l",NULL);//execlP("ls","ls","-l",NULL);execle("/bin/ls","ls","-l",NULL,envp); return 0;
}
运行结果:
execv:
应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
例如:
如char *arg[]这种形式,且arg最后一个元素必须是NULL
char *arg[] = {“ls”,”-l”,NULL};
演示代码:
execv.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
extern char **environ;
int main()
{ char *arg[]={"./hello",NULL}; execv("./hello",arg); return 0;
}
hello.c
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main()//循环打印环境变量
{printf("%s is called %d\n",__FILE__,__LINE__);while(*environ){printf("%s\n",*environ++);} return 0;
}
运行结果:
5.shell命令调用:
system
函数原型
#include <stdlib.h>
int system(const char *command);
函数功能
执行可执行程序
函数参数
command–要执行的可执行程序
函数返回值
参考man帮助
system函数通过调用shell程序/bin/sh–c来执行string所指定的命令,该函数在内部是通过调用fork、
execve(“/bin/sh”,…)、waitpid函数来实现的。通过system创建子进程后,原进程和子进程各自运行,相互间
关联较少。如果system调用成功,将返回0。
参考文章:
多进程初步
参考结构流程图:
多进程初步
注:需要使用xmind软件进行查看
这篇关于Linux7:多进程初步:进程描述,创建进程,特殊的进程,进程资源清理,进程退出,exec函数族以及system函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!