设为首页 加入收藏

TOP

进程控制(一)
2019-09-03 03:45:44 】 浏览:67
Tags:进程 控制

1. 程序和进程

什么是程序?什么是进程?

  • 程序是计算机存储系统中的数据文件,如源代码程序和可执行程序
  • 进程是程序关于某个数据集合的一次运行活动,是程序执行后得到的一个实体
  • 在当代操作系统中,进程是资源分配的基本单位

程序和进程有什么联系?

  • 没有程序就没有进程;但有了程序,未必就会有进程,如程序不运行、程序本身是动态库等
  • 一个程序可能对应多个进程,如记事本程序多次运行,产生多个记事本进程
  • 一个进程可能包含多个程序,如一个程序依赖多个动态库,每个动态库都是一个程序

2. 进程状态

进程三态模型:就绪、阻塞、运行。

  • 就绪:进程已经做好了一切准备,一旦得到CPU,就会开始运行
  • 阻塞:进程正在等待某一事件发生(如共享资源被释放、IO完成)而停止运行,在事件发生前,即使得到CPU也无法运行
  • 运行:进程拥有CPU控制权,并正在运行

进程五态模型:与三态模型相比,多了新建、终止两种状态。

  • 新建:进程还未创建完毕,不能被系统调度
  • 终止:进程已结束运行,正在回收系统资源

3. 进程标识

每个进程都有一个非负整数的进程ID(pid),作为识别不同进程的唯一标识。
此外,每个进程还有一些其他标识符,包括父进程ID(ppid)、实际用户ID(uid)、有效用户ID(euid)、实际组ID(gid)、有效组ID(egid)。

#include <unistd>

pid_t getpid();   //返回值:调用进程的进程ID
pid_t getppid();  //返回值:调用进程的父进程ID
uid_t getuid();   //返回值:调用进程的实际用户ID
uid_t geteuid();  //返回值:调用进程的有效用户ID
uid_t getgid();   //返回值:调用进程的实际组ID
uid_t getegid();  //返回值:调用进程的有效组ID

4. 进程创建

一个现有的进程可以调用fork函数创建一个新进程,这个新进程叫做子进程,调用fork的进程叫做父进程。

  • 子进程获得父进程数据空间、堆、栈的副本
  • 父进程和子进程共享代码段、文件描述符和文件偏移量
#include <unistd.h>

pid_t fork();  //若成功:子进程返回0,父进程返回子进程ID;若出错,返回-1

fork的特点为:一次调用,两次返回。

  • 父进程返回子进程ID的原因:父进程可以有多个子进程,但父进程不能通过函数获得其所有子进程的ID,因为没有这样的函数
  • 子进程返回0的原因:一个进程只会有一个父进程,并且子进程还可以调用getppid获得其父进程ID

fork有以下两种用法:

  • 父进程和子进程同时执行不同的代码段
  • 子进程从fork返回后立即调用exec,执行另一个不同的程序

fork成功返回后,父进程和子进程继续执行后面的代码,但父子进程谁先执行,这点是不确定的。

#include <stdio.h>
#include <unistd.h>

int globvar = 10;

int main()
{
    int var = 5;

    pid_t pid = fork();

    if (pid > 0)
    {
        sleep(2); //父进程休眠2秒,让子进程先运行
    }
    else if (pid == 0)
    {
        globvar++;
        var++;
    }

    printf("pid = %d, globvar = %d, var = %d\n", getpid(), globvar, var);

    return 0;
}

5. 进程终止

正常终止方式:

  • 从main函数return
  • 调用exit()、_Exit()、_exit()(对于linux,后两个函数是同义的)
  • 进程的最后一个线程在其启动例程中调用return或pthread_exit()

异常终止方式:

  • 调用abort()以产生SIGABRT信号
  • 进程接收到某些信号
  • 进程的最后一个线程对pthread_cancel()请求做出响应

6. 避免僵尸进程

僵尸进程的产生与危害

  • 一个已经终止、但是其父进程尚未对其进行善后处理的进程,称为僵尸进程
  • 子进程退出时,内核会释放它占用的内存等资源,但是仍然保留了一些信息,如进程ID
  • 内核为终止子进程保留的信息直到父进程调用wait或waitpid时才会释放
  • 如果父进程没有调用wait或waitpid,那么已经终止的子进程就会变成僵尸进程,其占用的进程ID会无法释放
  • 大量的僵尸进程可能会使系统没有可用的进程ID,从而导致系统无法创建新进程

wait函数

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status); //成功返回终止子进程ID,失败返回-1

当在父进程中调用了wait时:

  • 如果所有子进程都还在运行,则父进程阻塞
  • 如果有任意子进程终止,则取得其终止状态并立即返回
  • 如果父进程没有子进程,则立即出错返回

如果wait的参数status不为NULL,那么子进程的终止状态就存放在它指向的内存中,如果不关心终止状态,可以将status指定为NULL。

waitpid函数

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options); //成功返回终止子进程ID,失败返回-1或0

waitpid的第二个参数status用法和wait一样,但waitpid相比于wait的不同之处在于:

  • pid > 0时,waitpid可以等待由pid指定的特定子进程
  • options == WNOHANG时,若pid指定的子进程尚未终止,waitpid不会阻塞,而是立即返回0
  • pid == -1 && options == 0时,waitpid等价于wait

虽然waitpid可以实现非阻塞版本的wait,但也存在一个缺陷:如果子进程在父进程waitpid(pid, NULL, WNOHANGE)之后才终止,那么即使父进程尚未结束,也不会给子进程收尸,也就是说,终止的子进程会一直处于僵尸进程状态,直到父进程退出。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{    
    pid_t pid = fork();
    
    if (pid == 0)
    {
        sleep(2); //确保父进程执行waitpid时子进程还在休眠
        printf("child %d exit\n", getpid());  
        exit(0);      
    }
        
    if (waitpid(pid, NULL, WNOHANG) == 0)
    {
        printf("waitpid return before child exit\n");
    }
    
    sleep(300);   //虽然waitpid不阻塞,但在父进程终止前,子进程pid会一直是僵尸进程
        
    re
首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇操作系统原理之进程调度与死锁(三) 下一篇信号

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目