今天我们来说说进程!
进程
程序:硬盘上的可执行文件,是一堆静态代码。
进程:程序跑起来之后的动态实例,有自己内存、PID,经历创建、运行、消亡。
进程常用命令
• top:看进程CPU占用;
• ps‑ef:查看系统全部进程;
• ps‑aux:看更详细进程状态;
• kill -9 PID:强制干掉某个进程;
• &放在命令末尾,程序丢后台跑;fg把后台任务切回前台。
进程虚拟内存空间
每个进程拿到一套独立0‑4G虚拟内存,内核通过MMU映射到真实物理内存。
分段:
-
文本段:存放你的代码指令;
-
数据段:存全局、静态变量;
-
栈区:存局部变量,函数调用栈,函数执行完自动回收;
-
堆区:程序员手动malloc/free申请释放的内存。
进程状态
-
就绪态R:准备好了,等着CPU调度分配时间片;
-
运行态R:CPU正在跑这个进程;
-
可唤醒等待态S:在等某个资源,资源来了可以被唤醒接着跑;
-
不可唤醒等待态D:卡在IO,不能被信号唤醒;
-
停止态T:进程被暂停;
-
僵尸态Z:进程代码已经跑完退出了,但是父进程没回收它资源,残留PCB占系统资源;
-
结束态X:资源全部回收干净。
fork()
系统调用,用来创建子进程。调用一次,会返回两次值:
-
在父进程里面:返回子进程的PID号;
-
在新生成的子进程里面:返回0;
-
创建失败返回‑1。
底层写时复制COW:刚fork完父子进程共享物理内存,只有某一方要修改数据的时候,内核才复制那一块内存给子进程,不会全盘复制内存节省开销。fork之后父子进程是完全独立两套空间,子进程改变量不会影响父进程。
getpid() / getppid()
getpid()拿到自己这个进程的PID;getppid()拿到自己父进程的PID。
exit()
直接结束当前整个进程。注意区分return:return只是从函数返回;只有main里面return才等价exit,别的函数里面return仅仅退出函数,不会结束整个程序。
wait()
父进程调用,阻塞等待任意一个子进程死掉。子进程结束之后,wait回收子进程的残留资源,避免僵尸进程。
参数可以传一个int变量地址,用来存子进程退出的状态信息。成功返回死掉的子进程PID,失败‑1。
重点:wait会卡住父进程不动,直到有子进程结束。
waitpid()
比wait更灵活,可以指定等待某一个特定PID的子进程,还可以设置非阻塞模式(选项传WNOHANG,不卡父进程,子进程没结束就直接返回0)。
exec函数族(execl execlp execv execvp等)
exec不会创建新进程!PID不变,把当前进程里面全部旧代码、内存全部清空,加载磁盘上另一个新程序,从新程序main开始跑。
一般搭配fork:fork出来子进程,子进程调用exec跑别的程序。终端shell执行ls、pwd命令底层就是这套逻辑。
记忆后缀:
• l:参数用逗号列表一个个写;
• v:参数放字符串数组传进去;
• p:会去PATH环境变量路径里面找程序,不用写完整路径;
• e:可以自己给新进程设置环境变量。
setenv
设置环境变量,环境变量是进程全局可以访问的键值对,子进程默认会继承父进程环境变量。