通信进程

111111111111

  • linux中,进程通过数据结构task_struct表示
  • 进程上下文:是进程执行全过程的静态描述,包括指向可执行文件的指针、栈、数据、堆、进程状态、优先级、文件描述符、寄存器等等。
  • 进程启动:当内核启动C程序时(调用exec),先调用一个特殊的启动例程。可执行文件将此例程指定为程序的启动地址—这是由连接编译程序设置的,连接编译程序则有c编译程序调用。启动例程从内核取得命令行参数和环境变量,然后为调用main函数做好安排。

    进程终止

  • 正常终止:

    1. main处返回
    2. 调用exit
    3. 调用_exit
  • 异常终止
    1. 调用abort
    2. 由一个信号终止
  • _exit立即进入内核,而exit先执行一些清除处理(如调用各终止处理程序,关闭标准I/O),然后进入内核。
  • exit()函数与一exit()函数最大区别就在于exit()函数在调用exit 系统之前要检查文
    件的打开情况,把文件缓冲区的内容写回文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    int main() {

    printf("exit\n"); // printf函数遇到换行符时候回自动将缓冲区数据输出 因此不论使用exit还是_exit都会输出这一行
    printf("11111"); // 由于exit会输出缓冲区数据 因此会输出这一行
    exit(0);
    }
    // 输出为:
    // exit
    // 11111




    int main() {
    printf("_exit\n");
    printf("11111"); // _exit不会读出缓冲区数据,因此不会输出这一行
    _exit(0);
    }

    // 输出为:
    // _exit

    echo $? // 输出上一个进程的退出状态

    // 它的值有三种:
    // 1.程序冲main函数返回 保存为return的值 因此最好让main函数返回0
    // 2.程序调用exit(status)结束,保存为status的值
    // 3.程序异常退出,保存为错误码

c程序执行图解:

image

atexit 函数

  • 用于注册终止处理函数, 这些函数将会由exit自动执行。 exit按照这些函数的登记顺序的相反顺序来调用他们(最后注册的最先调用),同一函数登记多次也会多次调用。
    1
    2
    3
    #include <stdlib.h>
    //成功返回0 否则-1
    int atexit(void (*func)(void));

命令行参数

  • 当执行程序时候,调用exec的进程可以传递命令行参数给进程 (第0个参数不会传递 如下列的 “./test”)
  • ./test argv1 argv2 argv3
  • 1
    2
    3
    4
    5
    int main(int argc, char* argv[]) {
    for(int i = 0; i < argc; ++i) {
    printf("%s", argv[i]);
    }
    }

wait函数和waitpid函数

  • wait函数用于处理终止的子进程

    1
    2
    3
    4
    #include <sys/wait.h>
    pid_t wait(int *statloc);
    pid_t waitpid(pid_t pid, int *statloc, int options);
    //成功返回进程ID,失败返回0或-1
  • 这两个函数返回两个值,已终止子进程ID,以及通过statloc指针返回子进程终止状态(一个整数)。

  • 若调用wait的进程没有已终止的子进程,但有子进程在运行,则阻塞至第一个子进程终止为止。
  • waitpid函数:pid参数允许指定想等待的进程ID,-1表示等待第一个终止的子进程。options参数最常见选项为WNOHANG,它告知内核在没有终止子进程时不要阻塞。
  • fork子进程时必须捕获SIGCHLD信号。
  • 捕获信号时必须处理中断的系统调用。
  • SIGCHLD的信号处理函数应循环使用waitpid函数以免留下僵死进程(必须指定options参数为WNOHANG告知waitpid在有尚未终止的子进程运行时不要阻塞,不能在循环中调用wait因为没办法防止wait在正运行的子进程尚未终止时阻塞)。

C程序存储空间

从低地址到高地址依次为

  • 正文(.text):CPU执行的机器指令部分,通常只读且可共享。
  • 初始化数据段(.data):又称数据段,包含了程序中需要赋初值的变量,如C程序中任何函数之外的说明:

    1
    int MAX = 100;
  • 非初始化数据段(.bss),又称bss段,在程序开始之前,内核将其初始化为0,如函数外的说明:

    1
    long sum[100];
  • 堆:动态分配内存的场所。 有低地址向高地址生长。

  • 栈:存储自动变量和保存函数调用信息。由高地址向低地址生长。

  • 命令行参数和环境变量

进程创建
  • 在系统启动时,操作系统会创建一些进程,它们承担着管理和分配系统资源的任务,这些进程通常被称为系统进程。整个Linux 系统的所有进程也是一个树形结构。树根是系
    统自动构造的,即在内核态下执行的0 号进程,它是所有进程的祖先。由0 号进程创建1 号
    进程(内核态), 1 号负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高
    速缓存和虚拟贮存管理的内核线程。随后, l 号进程调用execve()运行可执行程序init ,并演
    变成用户态1 号进程,即init 进程。
    </br>
    即 0 号进程一> l 号内核进程一> l 号内核线程一> l 号用户. 进程( init 进
    程)一>getty进程一> shell 进程。1号内核进程执行init()函数并演变为1号用户进程init进程。这里init()函数为内核代码,它调用execve()从文件/etc/inittab中加载可执行文件init,这个过程并没有调用fork(),因此都是1号进程。
  • ID为0进是调度进程,常常被称为交换进程。该进程并不执
    行任何磁盘上的程序—它是内核的一部分,因此也被称为系统进程。

  • ID为1是init进程,init进程绝不会终止。他不是系统进程,只是普通进程,但是他以root权限运行。

  • ID为2程是页精灵进程。此进程负责支持虚存系统的请页操作。与交换进程一样,精灵进程也是内核进程。
僵尸进程

一个进程使用fork创建子进程,若子进程退出,而父进程并没有调用wait()或waitpid()系统调用来取得子进程状态,那么子进程的进程描述符会任然保留在系统中,即为僵尸进程。

孤儿进程

父进程退出后,其子进程仍然还在运行,即为孤儿进程。这些进程会被init进程收养并对他们完成状态收集工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
pid_t pid = fork();
if(pid > 0) { // 父进程
sleep(3);
}
else if(pid == 0) { // 子进程
exit(0);
}
return 0;
}

  • 这里子进程直接调用exit(0)退出,而父进程没有调用wait()来获取子进程退出状态,因此子进程便成为了僵尸进程。

    1
    ps aus | gerp -w 'Z' 可能查看僵尸进程
  • 当父进程睡眠3秒过后,父进程也接着退出,然后子进程由僵尸进程变成了孤儿进程,有init进程收养。init进程会对这些进程调用wait()。

守护进程

脱离于终端并在系统后台运行的进程。他不会被终端上的信息所打断。通常在系统引导装入是启动,系统关闭时候停止。创建如下:

  1. 创建子进程,父进程退出</br>在终端造成程序运行完毕的假象。之后工作都在子进程中完成。
  2. 在子进程创建新会话</br>setsid()创建一个新会话,并担任会话组组长。它的作用:摆脱原进程会话的控制,摆脱原进程组的控制,摆脱原终端的控制。
  3. 改变当前目录为根目录</br> chadir(“/“;)
  4. 重设文件掩码</br>umask(0)
  5. 关闭文件描述符</br>因为守护进程与终端失去了联系,因此文件描述符也失去了价值,应该关闭。通常如下:</br>
    1
    2
    3
    for(int i = 0; i < MAXFILE; ++i) {
    close(i);
    }
1
2
3
4
5
6
7
8
#include <sys/types.h>
#include <unistd.h>

pid_t getpid(); //返回进程id

pid_t getppid(); //返回父进程id

uid_t getuid(); //返回用户id
  • 上述函数都没有出错返回。

    fork函数
    1
    2
    3
    4
    5
    #include <sys/types.h>
    #include <unistd.h>

    pid_t fork(void);
    //fork执行一次 返回两次 在子进程返回0 父进程返回子进程id 出错返回-1
  • 子进程可以调用getppid获取父进程id。子父进程继续执行fork后的指令。子进程获得父进程的数据空间 堆 栈的复制品,但不共享存储空间。实际上是copy on write,即写时拷贝。他们只共享代码段(.text,参见内存布局)

  • fork子进程和父进程谁先执行取决于内核的调度。
    vfork
  • vfork用于创建一个新进程,而该新进程的目的是exec一个新进程。
  • vfork并不将父进程地址空间完全复制给子进程,因为子进程会立即调用exec(或exit),于是就不会访问改空间。且vfork保证子进程较父进程先运行,在他调用exec(或exit)后父进程才能被调度(如果在子进程调用exec或exit之前依赖于父进程某个动作则会导致死锁)。
  • 并且vfork出来的子进程与父进程共享内存。因此,在子进程修改父进程的变量会改变其值。
  • vfork()函数创建的子进程最后必须调用_exit()函数才能正确退出子进程,不能使用exit()函数。
  • 为什么在vfork出来的子进程调用return会是整个程序崩溃,而调用exit()不会??
  1. 子进程的main()函数return了,于是程序的函数栈发生了变化
  2. return后子进程会自动调用exit()类函数。
  3. 此时父进程收到子进程的exit()退出,开始从vfork返回,但是由于父子共享内存,此前的return使得父进程的函数栈也崩了,程序便崩溃。
  • 若vfork子进程调用exit()则没有修改函数栈,所以,父进程得以顺利执行。

  • 注意看这个面试题”https://coolshell.cn/articles/7965.html

    写时拷贝的实现

    操作系统赋予子进程属于他的页表,但这些页表都指向父进程的页面,同时把这些页面标记为只读。当进程(子或者父)试图向某一页表写时,会受到写保护的错误,此时操作系统意识到写行为,于是才会为该进程分配一个该页面的行副本。

进程通信

  • UNIX的进程通信有:

    1. 管道(半双工)
    2. 命名管道(FIFO)
    3. 流管道(全双工)
    4. 命名流管道
    5. 消息队列
    6. 信号量
    7. 共享存储
    8. 套接字
    9. 管道

  • 管道是所有UNIX支持的古老的IPC机制。管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。它:

    1. 半双工,数据只能在一个方向流动
    2. 只能在具有公共祖先进程之间使用。通常一个管道由一个进程创建,然后进程调用fork,此后父子进程可由管道通信。
    3. 管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消失。
    4. 面向字节流
    5. 管道内部保证同步机制,从而保证访问数据的一致性
  • 使用方法:</br>
    1.父进程创建管道,得到两个⽂件描述符指向管道的两端</br>
    2.父进程fork出子进程,⼦进程也有两个⽂件描述符指向同⼀管道。</br>
    3.父进程关闭fd[0],子进程关闭fd[1],即⽗进程关闭管道读端,⼦进程关闭管道写端(因为管道只支持单向通信)。⽗进程可以往管道⾥写,⼦进程可以从管道⾥读,管道是⽤环形队列实现的,数据从写端流⼊从读端流出,这样就实现了进程间通信。 </br>
1
2
3
4
#include <unistd.h>

//成功返回0 否则-1
int pipe(int filedes[2]);
  • filedes[0]为读端 filedes[1]为写端 且filedes[1]的输出是filedes[0]的输入,即写端的输出是读端的输入。
  • 若想数据从父进程流向子进程,则父进程关闭管道的读端(fd[0]),子进程关闭管道的写端(fd[1]);反之亦然。通常一个管道只有一个读进程和一个写进程。
  • 当读一个写端被关闭的管道在所有数据被读取后会返回0代表文件结束处。
  • 当写一个读端被关闭的管道会产生SIGPIPE信号。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    int main() {

    int n, fd[2];
    pid_t pid;
    char line[MAX_LINE];
    if(pipe(fd) < 0) {
    printf("error");
    exit(1);
    }
    if((pid = fork()) < 0) {
    printf("fork error");
    exit(1);
    }else if(pid > 0) {
    /*parent*/
    close(fd[0]);
    wirte(fd[1], "hhhhh", );
    }else {
    /*child*/
    close fd[1];
    n = read(fd[0], line, MAX_LINE);
    }
    exit(0);
    }

FIFO命名管道

  • 可用于不相关进程间的通信,是一种文件类型。建立后两个进程可以把他当做普通文件来读写。遵循先进先出原则,读操作总是从开始处返回数据,写操作是从结尾填入数据。

    1
    2
    3
    4
    5
    #include <sys/types.h>
    #include <sys/stat.h>

    // 成功返回0 否则-1
    int mkfifo(const char* pathname, mode_t mode);
  • 若第一个参数所指路径已经存在,则创建失败,返回EEXIST错误。

  • 一旦mkfifo创建成功后,可用文件I/O函数如open,close等操作它。通常应该设置为非阻塞
  • 当打开一个fifo时,非阻塞标志O_NOBLOCK影响:
    1. 一般情况若未说明非阻塞标志,只读打开阻塞直到某进程为写打开该FIFO。为写打开一个FIFO阻塞直到某进程为读打开。
    2. 若设置了非阻塞标志,则只读打开立即返回。但是若没有进程为读而打开FIFO,则只写打开出错返回,其error为ENXIO。
  • 若写一个无进程读而打开的FIFO,产生信号SIGPIPE,若某个FIFO最后一个写进程关闭了该FIFO,则该FIFO的读进程产生一个文件结束符。
  • 一个FIFO可以有多个写进程。

消息队列

  • 消息队列是消息的链接表,存放在内核中并用标识符标识。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>

    //打开一个现存队列或创建一个新队列
    int msgget(key_t key, int flag);

    // 当flag为IPC_CREAT时,若没有队列则创建队列,有返回已存在的队列
    // 当flag为IPC_EXCL时,没有返回-1,有返回0;

    // 向队列取出消息
    ssize_t msgrcv(int msgqid, void* msgp, size_t msgsz, log msgtyp, int msgflag);

    // 放消息
    int msgsend(int msgqid, const void* msgp, size_t msgsz, int flag);

    // msgid为消息队列标识码
    // msgp为指向消息缓冲区的指针,用于暂存消息
    // msgtyp为从队列读取的消息形态,为0表示队列所有消息都会被读取。
    // msgflag指明在消息队列没有数据时候的行动。若为IPC_NOWAIT,则msgsend时候若队列已满则立即返回-1。msgrcv是若队列为空也返回-1,并设置错误码为ENOMSG。若msgflag为0,则会阻塞等待。


    // 设置消息队列属性,即对消息对垒做cmd操作
    int msgctl(int msgqid, int cmd, struct msgid_ds* buf);

    // cmd有三种:
    // 1.IPC_STAT:获取消息队列对应的msgid_ds结构体并保存到buf中
    // 2.IPC_SET: 设置属性,其需设置的属性在buf中
    // 3.IPC_RMID: 从内核删除该消息队列

信号量

  • 这里信号量不适用于同步(POSIX 信号量),而是用与进程间通信。
  • 信号量的增减应该是原子操作,因此信号量通常在内核中实现。
  • 信号量的值为正,进程获得该资源的使用权,进程将信号量减1,表示它使用了一个资源单位
  • 若此时信号量的值为0,则进程进入挂起状态(进程状态改变),直到信号量的值大于0,若进程被唤醒则返回至上一步。
  • 相关头文件
  • 以下为SYSTEM V 信号量

    1
    2
    3
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
  • 创建信号量

    1
    2
    3
    4
    5
    6
    int semget(key_t key,int nsems, int flags);
    //返回:成功返回信号集ID,出错返回-1

    // key为函数通过调用ftok函数得到的键值
    // nsems为需要创建的信号量的初值,创建后不可更改
    // flags为读写权限 当创建信号量时不允许加IPC_CREAT
  • 删除一个信号量

    1
    int semctl(int semid, int semnum, int cmd, ...);

共享存储

  • 这是最快的一种IPC。

    1
    2
    3
    4
    5
    6
    #include <sys/shm.h>

    // 创建共享内存 失败返回-1 成功返回共享内存标识符 不同进程通过同一标识符便可以访问同一共享内存
    int shmget(key_t key, int size, int flag);

    // size为共享内存大小,以字节为单位
  • 当共享内存创建后,其他内存调用shmat连接到自身的地址空间中。

    1
    2
    3
    4
    5
    6
    7
    8
    void* shmmat(int shmid, void* addr, int flag);

    // 返回值为进程数据段所连接的实际地址
    // shmid为shmget返回值

    // 用于是某进程不能在使用该共享内存
    int shmdt(const void* shmaddr);
    // shmaddr为shmat返回值
请你来说一下共享内存相关api

参考回答:
Linux允许不同进程访问同一个逻辑内存,提供了一组API,头文件在sys/shm.h中。
1)新建共享内存shmget

int shmget(key_t key,size_t size,int shmflg);

key:共享内存键值,可以理解为共享内存的唯一性标记。

size:共享内存大小

shmflag:创建进程和其他进程的读写权限标识。

返回值:相应的共享内存标识符,失败返回-1

2)连接共享内存到当前进程的地址空间shmat

void shmat(int shm_id,const void shm_addr,int shmflg);

shm_id:共享内存标识符

shm_addr:指定共享内存连接到当前进程的地址,通常为0,表示由系统来选择。

shmflg:标志位

返回值:指向共享内存第一个字节的指针,失败返回-1

3)当前进程分离共享内存shmdt

int shmdt(const void *shmaddr);

4)控制共享内存shmctl

和信号量的semctl函数类似,控制共享内存

int shmctl(int shm_id,int command,struct shmid_ds *buf);

shm_id:共享内存标识符

command: 有三个值

IPC_STAT:获取共享内存的状态,把共享内存的shmid_ds结构复制到buf中。

IPC_SET:设置共享内存的状态,把buf复制到共享内存的shmid_ds结构。

IPC_RMID:删除共享内存

buf:共享内存管理结构体。

请你说一说Linux虚拟地址空间

参考回答:
为了防止不同进程同一时刻在物理内存中运行而对物理内存的争夺和践踏,采用了虚拟内存。
虚拟内存技术使得不同进程在运行过程中,它所看到的是自己独自占有了当前系统的4G内存。所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上。 事实上,在每个进程创建加载时,内核只是为进程“创建”了虚拟内存的布局,具体就是初始化进程控制表中内存相关的链表,实际上并不立即就把虚拟内存对应位置的程序数据和代码(比如.text .data段)拷贝到物理内存中,只是建立好虚拟内存和磁盘文件之间的映射就好(叫做存储器映射),等到运行到对应的程序时,才会通过缺页异常,来拷贝数据。还有进程运行过程中,要动态分配内存,比如malloc时,也只是分配了虚拟内存,即为这块虚拟内存对应的页表项做相应设置,当进程真正访问到此数据时,才引发缺页异常。

请求分页系统、请求分段系统和请求段页式系统都是针对虚拟内存的,通过请求实现内存与外存的信息置换。

虚拟内存的好处:

1.扩大地址空间;

2.内存保护:每个进程运行在各自的虚拟内存地址空间,互相不能干扰对方。虚存还对特定的内存地址提供写保护,可以防止代码或数据被恶意篡改。

3.公平内存分配。采用了虚存之后,每个进程都相当于有同样大小的虚存空间。

4.当进程通信时,可采用虚存共享的方式实现。

5.当不同的进程使用同样的代码时,比如库文件中的代码,物理内存中可以只存储一份这样的代码,不同的进程只需要把自己的虚拟内存映射过去就可以了,节省内存

6.虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中。当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。在内存中可以保留多个进程,系统并发度提高

7.在程序需要分配连续的内存空间的时候,只需要在虚拟内存空间分配连续空间,而不需要实际物理内存的连续空间,可以利用碎片

虚拟内存的代价:

1.虚存的管理需要建立很多数据结构,这些数据结构要占用额外的内存

2.虚拟地址到物理地址的转换,增加了指令的执行时间。

3.页面的换入换出需要磁盘I/O,这是很耗时的

4.如果一页中只有一部分数据,会浪费内存。

请你说一说操作系统中的缺页中断

参考回答:
malloc()和mmap()等内存分配函数,在分配时只是建立了进程虚拟地址空间,并没有分配虚拟内存对应的物理内存。当进程访问这些没有建立映射关系的虚拟内存时,处理器自动触发一个缺页异常。
缺页中断:在请求分页系统中,可以通过查询页表中的状态位来确定所要访问的页面是否存在于内存中。每当所要访问的页面不在内存是,会产生一次缺页中断,此时操作系统会根据页表中的外存地址在外存中找到所缺的一页,将其调入内存。

缺页本身是一种中断,与一般的中断一样,需要经过4个处理步骤:

1、保护CPU现场

2、分析中断原因

3、转入缺页中断处理程序进行处理

4、恢复CPU现场,继续执行

但是缺页中断是由于所要访问的页面不存在于内存时,由硬件所产生的一种特殊的中断,因此,与一般的中断存在区别:

1、在指令执行期间产生和处理缺页中断信号

2、一条指令在执行期间,可能产生多次缺页中断

3、缺页中断返回是,执行产生中断的一条指令,而一般的中断返回是,执行下一条指令。

修改最大句柄

  1. 针对当前进程

    1
    ulimit -n 新的数量
  2. 针对系统

    1
    2
    3
    4
    5
    vi /etc/security/limits.conf 添加

    *  soft  nofile  新的数量

    *  hard  nofile  新的数量

并发(concurrency)和并行(parallelism)

参考回答:

  • 并发(concurrency):指宏观上看起来两个程序在同时运行,比如说在单核cpu上的多任务。但是从微观上看两个程序的指令是交织着运行的,你的指令之间穿插着我的指令,我的指令之间穿插着你的,在单个周期内只运行了一个指令。这种并发并不能提高计算机的性能,只能提高效率。
  • 并行(parallelism):指严格物理意义上的同时运行,比如多核cpu,两个程序分别运行在两个核上,两者之间互不影响,单个周期内每个程序都运行了自己的指令,也就是运行了两条指令。这样说来并行的确提高了计算机的效率。所以现在的cpu都是往多核方面发展。

请问MySQL的端口号是多少,如何修改这个端口号

参考回答:
查看端口号:
使用命令show global variables like ‘port’;查看端口号 ,mysql的默认端口是3306。(补充:sqlserver默认端口号为:1433;oracle默认端口号为:1521;DB2默认端口号为:5000;PostgreSQL默认端口号为:5432)

修改端口号:

修改端口号:编辑/etc/my.cnf文件,早期版本有可能是my.conf文件名,增加端口参数,并且设定端口,注意该端口未被使用,保存退出。

线程需要保存哪些上下文,SP、PC、EAX这些寄存器是干嘛用的

参考回答:
线程在切换的过程中需要保存当前线程Id、线程状态、堆栈、寄存器状态等信息。其中寄存器主要包括SP PC EAX等寄存器,其主要功能如下:

  • SP:堆栈指针,指向当前栈的栈顶地址

  • PC:程序计数器,存储下一条将要执行的指令

  • EAX:累加寄存器,用于加法乘法的缺省寄存器

进程转换图

image

  1. 进程状态</br>

1)创建状态:进程正在被创建

2)就绪状态:进程被加入到就绪队列中等待CPU调度运行

3)执行状态:进程正在被运行

4)等待阻塞状态:进程因为某种原因,比如等待I/O,等待设备,而暂时不能运行。

5)终止状态:进程运行完毕

  1. 交换技术

当多个进程竞争内存资源时,会造成内存资源紧张,并且,如果此时没有就绪进程,处理机会空闲,I/0速度比处理机速度慢得多,可能出现全部进程阻塞等待I/O。

针对以上问题,提出了两种解决方法:

1)交换技术:换出一部分进程到外存,腾出内存空间。

2)虚拟存储技术:每个进程只能装入一部分程序和数据。

在交换技术上,将内存暂时不能运行的进程,或者暂时不用的数据和程序,换出到外存,来腾出足够的内存空间,把已经具备运行条件的进程,或进程所需的数据和程序换入到内存。

从而出现了进程的挂起状态:进程被交换到外存,进程状态就成为了挂起状态。

3、活动阻塞,静止阻塞,活动就绪,静止就绪

1)活动阻塞:进程在内存,但是由于某种原因被阻塞了。

2)静止阻塞:进程在外存,同时被某种原因阻塞了。

3)活动就绪:进程在内存,处于就绪状态,只要给CPU和调度就可以直接运行。

4)静止就绪:进程在外存,处于就绪状态,只要调度到内存,给CPU和调度就可以运行。

从而出现了:

活动就绪 —— 静止就绪 (内存不够,调到外存)

活动阻塞 —— 静止阻塞 (内存不够,调到外存)

执行 —— 静止就绪 (时间片用完)

Donate comment here