进程的概念
存储在存储器中的程序文件不是进程,只有当程序加载到内存中运行才能成为进程。进程是动态活动的实体。每个进程都有自己的虚拟内存空间。一个进程虚拟地址空间分布:
进程通过进程控制块**(PCB——Process Control Block)**管理,进程从生老到病死的一切信息进程控制块都有记录
进程是系统资源分配的基本单位。
进程标识
同人有唯一的身份证号一样,进程也有ID,称为pid。每个进程还包含ppid——父进程号。每个进程都有父亲。除了init进程,它是所有进程祖先。
特殊的进程
-
PID为0的进程
调度进程,内核的一部分,也被称为系统进程
-
PID为1的init进程
所有用户进程的祖先,负责引导系统、启动守护进程(后台进程),以超级用户权限运行
进程的状态
-
可执行态(TASK_RUNNING)
只有处于该状态的进程才可能占有CPU运行。
-
可中断睡眠态(TASK_INTERRUPTIBLE)
进程因等待某个事件发生而处于睡眠态,被挂起一直等待的事件发生,事件发生进程就被唤醒。 可以被中断。
-
不可中断睡眠态(TASK_UNINTERRUPTIBLE)
进程因等待某个事件发生而处于睡眠态,一旦等待的事件发生,进程就被唤醒。 不可以中断——不是不能响应外部中断,而是不能响应异步信号。
-
暂停状态or跟踪状态(TASK_STOPPED、TASK_TRACED)
进程收到SIGTOP就会进入TASK_STOPPED状态,除非该进程设置不响应该此信号。
程序被跟踪处于TASK_TRACE状态,例如使用GDB调试程序在进程中设置了断点执行到断点处进程就会停下来。TASK_TRACED状态的进程不能响应SIGCONT被唤醒。
-
进程死亡僵尸态(TASK_DEAD-EXIT_ZOMBIE)
不管进程是正常退出(return或者调用exit())还是异常终止,内核都会将进程设置为死亡僵尸状态。
进程占有所有资源,除了task_struct结构体。
父进程可以通过wait()/waitpid()查看子进程的死亡信息以及将其状态设置为EXIT_DEAD状态。
父进程先子进程死了,则由init进程负责收尸
父进程没空给子进程收尸,可以使用信号异步通知父进程收尸。
-
死亡退出态(TASK_DEAD-EXIT_DEAD)
进程彻底死亡的状态,进程所有一切被彻底释放。
创建进程
通过fork()系列系统调用创建。
fork()/clone()/vfork()
fork()创建子进程
man fork 查看fork()函数用法
#include <unistd.h>
#include "stdio.h"
int main(int argc, char **argv)
{
pid_t pid;
printf("before fork\r\n");
pid = fork(); //创建子进程,子进程从fork()后面的第一条语句开始执行,所以after fork会执行两次
printf("after fork\r\n");
return 0;
}
根据pid区别父子进程
#include <unistd.h>
#include <sys/types.h>
#include "stdio.h"
int main(int argc, char **argv)
{
pid_t pid;
printf("before fork\r\n");
pid = fork();
if(pid == 0) //子进程
{
while(1)
{
printf("pid : %d, I am child\r\n", getpid());
printf("\r\n");
sleep(1);
}
}
else //父进程
{
while(1)
{
printf("my child pid : %d\r\n", pid);
printf("pid : %d, I am father\r\n", getpid());
sleep(2);
}
}
return 0;
}
子进程中fork函数返回值为0,父进程中fork返回值为子进程的pid
一般让子进程做与父进程不同的事情
子进程去执行准备好的elf文件或者脚本,不然子进程和父进程做一样的事情没什么意义。
涉及的函数接口:
exce()函数族
man exce可以查看有哪些函数
#include <unistd.h>
#include <sys/types.h>
#include "stdio.h"
int main(int argc, char **argv)
{
pid_t pid;
printf("before fork\r\n");
pid = fork(); //创建子进程
if(pid == 0) //子进程
{
execl("child_work", "child_work", NULL); //执行准备好的elf程序,原来子进程和父进程一样的程序就会被覆盖
printf("diu\r\n"); //exec函数族函数执行后不会返回,所以后面这条语句不会执行
}
else //父进程
{
while(1)
{
printf("my child pid : %d\r\n", pid);
printf("pid : %d, I am father\r\n", getpid());
sleep(1);
}
}
return 0;
}
child_work.c:
#include "stdio.h"
#include <unistd.h>
int main(int argc, char **argv)
{
while(1)
{
printf("hello world\r\n");
sleep(1);
}
return 0;
}
子进程是从父进程中复制过来,类似细胞分裂。
类似基因的遗传性,子进程以下属性在创建之初和父进程一样:
- 实际UID和GID,以及有效UID和GID
- 所有环境变量
- 进程组ID和会话ID、
- 当前工作路径,可用chdir()修改
- 打开的文件
- 信号响应函数
- 内存空间(栈、堆、bss段、标准IO缓冲区等)
类似基因的多样性,子进程以下属性和父进程不同:
- 进程号PID
- 记录锁
- 挂起的信号