进程控制
1.进程切换
进程切换(上下文切换)
暂停当前运行进程,从运行状态变成其他状态,调度另一个进程从就绪状态变成运行状态
进程切换的要求
切换前,保存进程上下文;切换后,恢复进程上下文;快速切换
进程生命周期的信息
寄存器 (PC, SP, …);CPU状态;内存地址空间
进程控制块PCB:内核的进程状态记录
内核为每个进程维护了对应的进程控制块(PCB),内核将相同状态的进程的PCB放置在同一队列
主要有就绪队列,IO等待队列,僵尸队列
2.进程创建fork
创建新进程
Windows进程创建API: CreateProcess(filename)
创建时关闭所有在子进程里的文件描述符 CreateProcess(filename, CLOSE_FD)
创建时改变子进程的环境 CreateProcess(filename, CLOSE_FD, new_envp)
https://blog.youkuaiyun.com/qq_34490018/article/details/80517329
Unix进程创建系统调用: fork/exec
fork()把一个进程复制成二个进程 parent (old PID), child (new PID)
exec()用新程序来重写当前进程 PID没有改变
https://blog.youkuaiyun.com/c1204611687/article/details/85613492
fork()系统调用
int pid = fork(); // 创建子进程
if(pid == 0) { // 子进程在这里继续
// Do anything (unmap memory, close net connections…)
exec(“program”, argc, argv0, argv1, …);
}
fork() 创建一个继承的子进程,复制父进程的所有变量和内存,复制父进程的所有CPU寄存器(有一个寄存器例外)。
fork()的返回值,子进程的fork()返回0,父进程的fork()返回子进程标识符,fork() 返回值可方便后续使用,子进程可使用getpid()获取PID
fork()的地址空间复制
fork()执行过程对于子进程而言,是在调用时间对父进程地址空间的一次复制,对于父进程fork() 返回child PID, 对于子进程返回值为0。
3.进程加载和执行exec
系统调用exec( )加载新程序取代当前运行进程
允许进程“加载”一个完全不同的程序,并从main开始执行(即_start)
允许进程加载时指定启动参数(argc, argv)
exec调用成功时,它是相同的进程,但是运行了不同的程序
代码段、堆栈和堆(heap)等完全重写
执行fork系统调用,pid返回值小于0,说明fork失败;pid=0,说明为子进程;pid>0,说明为父进程;
在子进程中执行exec调用,可以实现加载新程序来执行
main()
…
int pid = fork(); // 创建子进程
if (pid == 0) { // 子进程在这里继续
exec_status = exec(“calc”, argc, argv0, argv1, …);
printf(“Why would I execute?”);
} else { // 父进程在这里继续
printf(“Whose your daddy?”);
…
child_status = wait(pid);
}
if (pid < 0) { /* error occurred */
在shell中调用fork()后加载计算器的图示
在shell中调用fork()后加载计算器的图示
执行fork调用,内核中持有两个PCB控制块,子进程是对于父进程的拷贝;
当加载exec时,子进程代码段,数据段,堆栈段被覆写,来执行新的程序代码
int main()
{
pid_t pid;
int i;
for (i=0; i<LOOP; i++)
{
/* fork another process */
pid = fork();
if (pid < 0) { /*error occurred */
fprintf(stderr, “Fork Failed”);
exit(-1);
}
else if (pid == 0) { /* child process */
fprintf(stdout, “i=%d, pid=%d, parent pid=%d\n”,I,
getpid() ,getppid());
}
}
wait(NULL);
exit(0);
}
fork()的开销
fork()的实现开销
对子进程分配内存,复制父进程的内存和CPU寄存器到子进程里
在99%的情况里,在调用fork()之后调用exec(),在fork()操作中内存复制是没有作用的,子进程将可能关闭打开的文件和连接
vfork() 创建进程时,不再创建一个同样的内存映像,创建进程时,不再创建一个同样的内存映像,子进程应该几乎立即调用exec()
使用 Copy on Write (COW) 技术
4.进程等待wait和终止exit
父进程等待子进程
wait()系统调用用于父进程等待子进程的结束,子进程结束时通过exit()向父进程返回一个值,父进程通过wait()接受并处理返回值
wait()系统调用的功能
有子进程存活时,父进程进入等待状态,等待子进程的返回结果,当某子进程调用exit()时,唤醒父进程,将exit()返回值作为父进程中wait的返回值;
有僵尸子进程等待时,wait()立即返回其中一个值;
无子进程存活时,wait()立刻返回;
进程的有序终止 exit()
进程结束执行时调用exit(),完成进程资源回收
exit()系统调用的功能
将调用参数作为进程的“结果”
关闭所有打开的文件等占用资源
释放内存
释放大部分进程相关的内核数据结构
检查是否父进程是存活着的,如存活,保留结果的值直到父进程需要它,进入僵尸(zombie/defunct)状态;如果没有,它释放所有的数据结构,进程结果
清理所有等待的僵尸进程
进程终止是最终的垃圾收集(资源回收)
进程僵尸状态的含义:子进程执行exit调用,释放了相关资源,保存了执行结果,此时父进程仍然存活,因此子进程需要等待父进程是否需要使用子进程的结果,此时子进程处于僵尸状态。
参考:清华大学 操作系统 陈渝 http://os.cs.tsinghua.edu.cn/oscourse/OS2015/