先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
一 why
我们要学习进程编程呢?
在我们实际的项目开发过程中,一个实际的软件项目肯定要实现各种不同的功能,这些功能放在一个进程中实现肯定是不现实的,这样会造成项目十分冗余和繁杂,不适合项目的维护,也不适合项目的后续框架升级。因此,在实际的项目中,我们都会使用多进程的方式实现。
本篇文章就谈谈如何实现进程编程,主要从进程的创建,进程的结束,进程的回收,进程中的exec函数族,以及在看一个守护进程的实现方式
二 what
在这个系列的第一篇文章《linux基本概念(一)----进程和线程》中,我们谈到了进程的基本概念以及进程的组成,我们知道进程就是一个具有独立功能的可执行程序在一个数据集合上的动态执行过程,那么我们应该如何实现一个进程呢
(a)创建进程
我们使用fork函数来创建一个进程
函数原型:
#include <unistd.h>
pid_t fork(void);
返回值: 成功时,父进程返回子进程的进程号,子进程返回0
通过fork的返回值区分父子进程
这个函数的返回值和其他接触过的绝大部部分函数的返回值不一样,一般地,一个函数的返回值只有一个值;但是该函数的返回值却有两个,这个需要我们大家重点关注。实际上关于这个函数的返回值还是一个,只不过它的返回值在不同两个不同的进程中,每个进程返回值不一样而已,从程序的角度来看,给我们感觉它返回了两个值。
基本概念:
1. 父进程: 执行fork函数的进程
2. 子进程: 父进程调用fork函数之后,生成的一个新进程
在Linux中有这样一个概念: 一个进程必须是另外一个进程的子进程,或者说一个进程必须有父进程,但是不一定有子进程
3. 子进程继承了父进程的内容,包括父进程的代码,变量,pcb,甚至包括当前PC值
4. 父子进程有独立的地址空间,有独立的代码空间,互不影响,也就是说就算在父子进程中有同名的全局变量,但是他们都处在不同的地址空间,因此无法共享,无法互相访问。
5. 子进程结束之后,必须由它的父进程回收它的一切资源,否则就会成为一个僵尸进程,也就是说虽然一个子进程结束运行了,但是它的PCB控制块仍然挂载内核管理的链表上,内核仍然认为该进程存在
6. 如果父进程先结束,子进程会成为孤儿进程,它会被INIT收养,INIT进程是内核启动后,首先被创建的一个进程,此时子进程成为后台进程,它只能想console输出,不能接受来自console的输入
思考如下问题
(1)子进程被创建之后,子进程从何处开始运行?
上面我们说到,当创建一个子进程之后,子进程会继承父进程的内容,包括父进程的代码,变量,pcb,甚至包括当前PC值,请注意“当前PC值”,显然在子进程中,PC值是父进程当前的PC值,而父进程中的当前PC值是指向执行fork函数的下一条指令值,因此子进程会从fork函数的下一条指令处开始运行。
(2)子进程和父进程谁先运行
这个执行顺序是不确定的,它取决于操作系统的调度,可能先运行父进程,也可能先运行子进程
(3)父进程能够多次调用fork函数?子进程呢?
父子进程都可以多次调用fork函数,这样父进程会创建多个子进程,子进程也会创建多个它的子进程
(b)结束进程
1. exit
原型
2 _exit
#include <unistd.h>
void _exit(int status);
3. 区别
头文件不一样 #include "stdlib.h" #include <unistd.h>
exit结束进程时会刷新缓冲区,__exit不会
(c)回收进程
1. wait
pid_t wait(int *status)
成功返回子进程的进程号,失败返回EOF
若子进程没有结束,父进程一直阻塞
若有多个子进程,哪个先结束就先回收
status 指定保存子进程返回值和结束方式的地址
status 为NULL表示直接释放子进程PCB,不接受返回值
2. waitpid
pid_t waitpid(pid_t pid, int *status, int options);
如何在一个进程中执行另外一个进程呢?
exec函数族就是为了实现这个目的
(a)execl/execlp
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
(b)execv/execvp
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
(c)system
int system(const char *command);
守护进程
1. 特点
始终在后台运行
独立于任何终端,和终端无关,在运行的时候不能访问终端
周期性执行某种任务或者等待处理特定事件
2. linux以会话(session),进程组的方式管理进程
每个进程对应一个进程组
会话是一个或多个进程组的集合,通常用户打开一个终端时,系统会创建一个会话。所有
通过该终端运行的进程都属于这个会话
终端关闭时,所有相关进程会被结束
为什么会提到守护进程呢?先看下面一段代码,不知道大家有没有疑问?
#include <unistd.h>
#include "stdio.h"
#include "stdlib.h"
int main()
{
while(1);
}
首先上面一段程序当开始运行时,它是一个进程,对吧?那么问题来了,既然创建一个进程需要执行fork函数,那么在上面的源代码中,我们并没有执行fork来函数来创建一个进程,为什么运行时,它会是一个进程呢?肯定是由别的进程帮我们创建了这个进程,那么谁帮我们创建了这个进程呢?
它就是shell交互进程来实现的,我们试想一下当我们编译完上面的程序生成一个可执行程序的过程中,我们会在shell下执行如下命令
./test_fork //名字无所谓,看我们编译时指定生成的可执行文件名
在我们敲完回车键之后,并不是立马执行这个可执行程序的,而是首先由shell交互进程创建了一个子进程,来执行我们的这个程序,具体细节就不在这里展开了,有时间会花一篇博文单独介绍。
三 how
(1)创建一个子进程
#include "stdio.h"
#include "stdlib.h"
#include <unistd.h>
#include "sys/types.h"
unsigned int pthread_inter = 0;
void *fun(void *var)
{
int j;
while(!pthread_inter);
for (j = 0; j < 10; j++) {
usleep(100);
printf("this is fun j=%d\n", j);
}
}
int main()
{
pid_t pid;
int process_inter = 0;
pid = fork();
if (pid == 0) { // child process
int i = 0;
while (process_inter == 0);
for (i = 0; i < 10; i++) {
usleep(100);
printf("this is child process i=%d\n", i);
}
}
if (pid > 0) { // parent process
int i = 0;
for (i = 0; i < 10; i++) {
usleep(100);
printf("this is parent process i=%d\n", i);
}
process_inter == 1;
while(1);
return 0;
}
(2)进程退出以及回收
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int status = -1;
pid_t pid;
pid = fork();
if (pid == 0){
printf("fork\n");
exit(2);
} else if (pid > 0) {
pid = waitpid(pid, &status, 0);
printf("status=0x%x\n", status);
} else {
perror("fork\n");
}
return 0;
}
(3)exec函数族
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int ret = 0;
#if 0
ret = execl("/bin/ls", "ls", "-a", "-l", "/etc", NULL);
if (ret < 0) {
perror("execl");
}
#endif
#if 0
ret = execlp("ls", "ls", "-a", "-l", "/bin", NULL);
if (ret < 0) {
perror("execlp");
}
#endif
char *argv[] = {"ls", "-a", "-l", "/etc", NULL};
#if 0
ret = execv("/bin/ls", argv);
if (ret < 0) {
perror("execv");
}
#else
ret = execvp("ls", argv);
if (ret < 0) {
perror("execvp");
}
#endif
return 0;
}