当前位置 博文首页 > 司夏的博客:操作系统 - 进程控制

    司夏的博客:操作系统 - 进程控制

    作者:[db:作者] 时间:2021-08-06 21:56

    进程切换

    进程切换(上下文切换)

    • 暂停当前运行进程,从运行状态变成其他状态
    • 调度另一个进程从就绪状态变成运行状态

    进程切换的要求

    • 切换前,保存进程上下文
    • 切换后,恢复进程上下文
    • 快速切换

    进程生命周期的信息

    • 寄存器 (PC, SP, …)
    • CPU状态
    • 内存地址空间

    上下文切换图示

    在这里插入图片描述

    进程控制块PCB:内核的进程状态记录

    内核为每个进程维护了对应的进程控制块(PCB)

    内核将相同状态的进程的PCB放置在同一队列

    • 就绪队列
    • I/O等待队列,每个设备一个队列
    • 僵尸队列

    在这里插入图片描述

    创建新进程

    Windows进程创建API: CreateProcess(filename)

    • 创建时关闭所有在子进程里的文件描述符 CreateProcess(filename, CLOSE_FD)
    • 创建时改变子进程的环境 CreateProcess(filename, CLOSE_FD, new_envp) 等等

    Unix进程创建系统调用: fork/exec

    • fork()把一个进程复制成二个进程 ,parent (old PID), child (new PID)
    • exec()用新程序来重写当前进程,PID没有改变

    用fork和exec创建进程的示例

    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

    在这里插入图片描述

    程序加载和执行

    系统调用exec( )加载新程序取代当前运行进程

    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 */
    

    fork()使用示例

    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()

    • 创建进程时,不再创建一个同样的内存映像
    • 一些时候称为轻量级fork()
    • 子进程应该几乎立即调用exec()
    • 现在使用 Copy on Write (COW) 技术

    程序加载和执行系统调用exec()

    允许进程“加载”一个完全不同的程序,并从main开始执行(即_start)

    允许进程加载时指定启动参数(argc, argv)

    exec调用成功时

    • 它是相同的进程…
    • 但是运行了不同的程序

    代码段、堆栈和堆(heap)等完全重写

    父进程等待子进程

    wait()系统调用用于父进程等待子进程的结束

    • 子进程结束时通过exit()向父进程返回一个值
    • 父进程通过wait()接受并处理返回值

    wait()系统调用的功能

    • 有子进程存活时,父进程进入等待状态,等待子进程的返回结果,当某子进程调用exit()时,唤醒父进程,将exit()返回值作为父进程中wait的返回值
    • 有僵尸子进程等待时,wait()立即返回其中一个值
    • 无子进程存活时,wait()立刻返回

    进程从退出开始,到它的退出状态信息被父进程的wait/waitpid读走,在这个阶段中,进程将仍然存在于内核进程表中,这个阶段的进程的状态是终止态或称为僵尸态。而僵尸态进程称为僵尸进程,而父进程读子进程退出状态信息的动作则称为”reap”(这是一个术语,含义是收割、收走),即为子进程收尸。

    进程的有序终止 exit()

    进程结束执行时调用exit(),完成进程资源回收

    exit()系统调用的功能:

    • 将调用参数作为进程的“结果”
    • 关闭所有打开的文件等占用资源
    • 释放内存
    • 释放大部分进程相关的内核数据结构
    • 检查是否父进程是存活着的,如存活,保留结果的值直到父进程需要它,进入僵尸(zombie/defunct)状态,如果没有,它释放所有的数据结构,进程结果
    • 清理所有等待的僵尸进程

    进程终止是最终的垃圾收集(资源回收)

    其他进程控制系统调用

    优先级控制

    • nice()指定进程的初始优先级
    • Unix系统中进程优先级会随执行时间而衰减

    进程调试支持

    • ptrace()允许一个进程控制另一个进程的执行
    • 设置断点和查看寄存器等

    定时

    • sleep()可以让进程在定时器的等待队列中等待指定

    进程控制 v.s. 进程状态

    在这里插入图片描述

    cs
    下一篇:没有了