进程、线程
进程就是正在运行的程序,或程序的一次执行过程。
通过同一程序可以产生多个进程(即所谓的多开)。
操作系统(Operating System,简称OS)会给每个进程分配一个不同的整数编号(大于或等于1),这个编号就是所谓的PID(Process Identification,进程 ID),操作系统就是通过 PID区分和管理不同的进程。
进程是操作系统分配资源(PID、私有地址空间)的基本单位。一旦进程结束后,操作系统会自动回收进程所有的资源。
系统中的所有进程可以并发或并行执行。
特点:多任务并发,高并发。
多进程技术可以实现多任务并发处理,但系统中的进程并不是越多越好,并且同时执行的进程数是有上限的。如果进程数太多,会消耗系统很多资源,导致系统性能急剧下降甚至无法正常运行,新进程无法继续创建。
父进程与子进程
Bootloader 程序:引导加载程序
-
程序(Program):一个可执行的文件。
-
软件(Software):程序+文档。
-
硬件(Hardware):
-
fork,vfork,exec,system,popen
-
getgid获得本进程ID,getppid获得父进程ID
-
exit结束当前进程,_exit
-
waitpid,wait
fork系统调用
fork:创建一个新的子进程,新的子进程与父进程完全一样,直接拷贝了父进程的地址空间。创建完成后,父进程和子进程将会同时运行(并发运行)。“一次调用,两次返回”,但它在父进程和子进程中的返回值是不一样的,子进程返回0,父进程返回子进程PID。
通过PID查进程的名称
ps -e | grep 进程PID
-
孤儿进程:调用 fork 后,父进程先结束,子进程就成为了孤儿进程,孤儿进程会被系统进程(init/systemd)自动收养,成为其子进程。
-
僵尸进程(Zombie Process):调用 fork 后,子进程先结束了,但父进程没有为其“收尸”,子进程就变成成了僵尸进程虽然僵尸进程结束了,但其PID、推出状态信息等资源还没有被系统释放(内存空间会被释放),直到父进程“收尸”处理或父进程结束。僵尸进程会白白浪费系统资源,如果数量很多,会严重影响系统性能,所以我们要避免僵尸进程出现。
fork 函数效率很高,内部采用“写时拷贝”机制,与父进程共享空间,如果不改变变量值,就一直共享内存空间。
wait函数
#include <sys/types.h>
#include <wait.h>
int wait(int *status)
wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下的返回值为子进程的PID。
用法一:wait(NULL)用以杀死僵尸进程
成功会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
用法二:wait(int *status)
参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中, 这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的,以及正常结束时的返回值,或被哪一个信号结束的等信息。
1,WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
(请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数–指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了。)
2, WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。
https://blog.csdn.net/wyhh_0101/article/details/83933308
vfork系统调用
vfork:创建一个新的子进程,返回值与fork一样,不一样的是vfork创建子进程后,子进程先运行,父进程会一直阻塞(Blocked),直到子进程结束(调用 _exit 函数)或调用了exec 系列函数后,父进程才会被调度运行。子进程并没有拷贝父进程的地址空间,而是与其共享。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int g = 1;
int main()
{
int i = 1;
pid_t pid = vfork();//改成fork不会等待子进程
if(pid < 0) perror("vfork");
if(pid == 0)
{
g++;
i++;
printf("我是子进程!(g = %d, i = %d)\n",g, i);
sleep(3);//延时三秒
_exit(0);//结束进程,子进程与父进程共用一片缓冲区
}
else
{
printf("我是父进程!(g = %d, i = %d)\n",g, i);
}
return 0;
}
运行结果:
我是子进程!(g = 2, i = 2)
我是父进程!(g = 2, i = 2)
若为fork运行结果:
我是父进程!(g = 1, i = 1)
我是子进程!(g = 2, i = 2)
exec 函数
exec 系列函数会加载新程序的指令和数据到进程地址空间通常和 fork 和 vfork 函数配合使用(在子进程中调用 exec),调用时,直接清空当前进程,执行命令内的程序,如果需要父进程等待子进程可以调用wait(NULL)或者wait(pid,)
system()函数内部就是fork与execl的结合
_exit 函数:Linux 系统调用,结束当前进程。
exit 函数:标准 C 库函数,在Linux 系统上,该函数内部首先会逆序调用所有已经注册好的退出函数,然后清空 I/O 缓冲区,最后再调用 _exit 函数。
atexit(fun1):注册一个函数,在程序退出(exit)前调用一次fun1函数,并且越先注册的函数越慢执行(栈属性)
回调函数(Callback Function):定义之后并没有显式调用,而是在发生某个事件或满足某个条件时会被自动调用的函数。
system:标准 C 库函数,执行一个shell命令,在命令完成之后才能返回。
//内部实现
int system(const char *command)
{
pid_t pid = fork();//产生一个进程
if(pid == 0)
{
return 1;
}
else
{
return execl("/bin/sh","sh","-c",command,(char *)NULL);//执行command命令
}
int status;
waitpid(pid, &status, 0);//等待进程结束
return status;
}