初识进程----

一、概念
进程:程序的一次动态执行过程。每个进程都有自己的状态和自己的虚拟地址空间,是操作系统分配资源的基本单位。
程序:为了完成一系列任务的特定指令的有序集合。
进程和程序的区别:
1、进程是动态的,程序的静态的
2、一个程序可以对应多个进程,但一个进程只能对应一个程序
3、程序是代码+数据,进程是:代码+数据+堆栈+PCB。
二、创建
创建一个进程的一般工作:
1、分配一个PID(0 - /proc/sys/lernel//pid_max)
0号进程是内核进程,它创建一号进程
0号进程还将进程从物理内存搬运到磁盘或者从磁盘搬运到物理内存
查看一个进程:ps -ef |grep 文件名
2、分配PCB,拷贝父进程的绝大部分数据
3、给子进程分配资源
4、复制父进程的地址空间
4、将子进程置为就绪状态,放入就绪队列。

pid_t fork(void)(创建子进程),如果是子进程返回值为0,如果不是子进程,返回值不是0父进程返回值为1;
fork:是一次调用,两个返回。
可以通过系统调用来获取进程提示符:
进程id:PID;
父进程id:PPID;是由shell终端启动起来的



kill + 进程号:干掉一个进程
fork注意点:
1、fork父子进程 交替运行。
2、父进程死亡,子进程变为孤儿进程,由1号进程领养。
3、子进程死亡,变为僵尸进程

在linux系统中输入 ps -l 会出现以下内容:

UID:代表 执行者的身份;
PID:代表这个进程的进程号;
PPID:代表这个进程是由哪个进程发展衍生而来,也就是父进程的代号;
PRI:代表这个进程可被执行的优先级,其值越小则越早被执行;
NI:代表 这个进程的nice值;
三、环境变量
在当前进程及其子进程中使用。
export name=val(注意:=左右不能有空格)
unset name:取消环境变量
env:获取全部环境变量
echo $环境变量名 打印出相应的环境变量值
当前终端下设置的环境变量只能在当前终端下使用,离开就没有了
如果想让所有的地方都能使用环境变量的话,就需要在~/.bash_profile配置,如果希望在配置文件设置的变量生效,需要重启。
获取环境变量
1、获取所有环境变量,main的第三个参数
2、获取某一环境变量:val=getenv(“name”);
在程序中设置环境变量:
putenv(“name=value”);


四、 进程的销毁:
1、释放资源
2、记账信息
3、将进程状态设置为僵尸状态
4、转存储调度
五、进程终止的方法:
正常退出:
1、main函数退出
2、exit()(c库函数,1、执行退出处理程序;2、刷新IO缓存)
3、_exit();(操作系统提供的接口,让进程终止)
异常退出:
1、ctrl+c
2、assert()
3、abort()
4、信号终止
echo $?: 查看进程的退出方法(是否异常)。
exit(0)、exit(1)、_exit()、return 的区别:
exit(0):表示正运行程序并且退出程序
exit(1):非正常运行导致退出程序
_exit():用来终止一个进程,区别是_exit会立刻进入内核,而exit会先 执行一些清除工作(包括执行各种终止处理程序,关闭所有标准I/O等,一旦关闭了I/O,向printf等函数 就不会输出任何东西),再进入内核。
return:是语言级别的,它表示了调用堆栈的返回

六、僵尸子进程和孤儿进程:
子进程死亡,变为僵尸进程。
父进程死亡,子进程变为孤儿进程。
分析:每个进程退出的时候,内核将会释放其所有的资源,包括打开文件,占用的内存,但是仍然会保留一些信息,如进程号,退出状态,运行时间等。直到父进程通过wait/waitpid才来释放,这样就导致了问题,但是进程不调用wait的话,僵尸进程的回收就成了问题,将会一直占用这个进程号,由于系统的进程号是有限的,因此将可能产生没有进程号而无法产生新进程,所以,僵尸进程是有很大的危害的。
但是孤儿进程是不同的,父进程死后 ,将会有有init进程对他们进行状态收集工作。所以孤儿进程不会有什么危害。
孤儿进程实例:

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

int main(void)
{
        pid_t pid=fork();//创建一个进程
        if(pid<0)
                perror("fork error!"),exit(1);//创建失败
        if(pid==0){
                printf("I am a child\n");
                printf("pid:%d,ppid:%d\n",getpid(),getppid());//获得子进程和父进程的进程号
                printf("sleep 5s\n");
                sleep(5);
                printf("pid:%d,ppid:%d\n",getpid(),getppid());//获得子进程和父进程的进程号
                printf("child is exited\n");
        }
        else{
                printf("I am a parent\n");
                printf("pid:%d\n",getpid());
                printf("sleep:1s\n");
                sleep(1);
                printf("parent is exited\n");
        }
}

七、回收僵尸子进程:
子进程死亡,变为僵尸进程。
父进程死亡,子进程变为孤儿进程。
pid_t wait(int *status);这个函数是阻塞的,知道有子进程死亡,才会退出。(父进程回收僵尸子进程的id)
返回值:被回收子进程的ID
其中:EXIT_FAILURE,作为exit()的一个参数,代表没有成功的执行一个程序。
EXIT_SUCCESS:作为exit()的一个参数来使用,代表成功的执行了一个程序。

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

int main(void)
{
        pid_t pid=fork();
        if(pid==-1)
                perror("fork"),exit(EXIT_FAILURE);

        if(pid==0){
                sleep(2//20);
                printf("哈哈,再见 pid=%d\n",getpid());
                exit(125);//进程终止
        }else{
                int s;
                pid_t r=wait(&s);//父进程回收
                printf("r=%d\n",r);
                if(WIFEXITED(s)){
                        printf("code=%d\n",WEXITSTATUS(s));
                }

        }
}

status:8—16:是子进程的,退出码。
WIFEXITED(status):如果正常退出返回真
WEXITSTATUS(status):返回子进程的退出码
结果:子进程2秒后死亡,exit退出这个子进程,然后 父进程可以通过wait回收这个子进程的id


这个操作之后,进程将会结束:

八、进程调度:
常见的进程调度算法 :
在操作系统中的调度相当于是一种资源分配。
1、先来先服务算法(FCFS):
先来先服务 算法是一种最简单的调度算法,既可用于作业调度,也可以用于进程调度。当作业调度使用该算法时,每次调度都是从后备作业队列中选择一个或者多个最先进入该队列的作业,将他们调入内存,为他们分配资源,创建进程,然后放入就绪队列里面。当进程调度使用该算法时,每次的调度就是从就绪队列中选择一个最先进入该队列的进程,为之分配处理机,使之投入运行。该进程一直运行到完成或者发生某事件而阻塞后才放弃处理机。
2、短作业(进程)优先调度算法(SJ(P)F)
是指对短作业或者短进程优先调度算法,它可以分别用于作业调度和进程调度。短作业优先调度算法是从就绪队列中选出一优先调度算法是从就绪队列中选出一个个或者若干个估计运行时间最短的作业,将它们调入内存运行。而短进程有限调度算法则是在就绪队列中选出一个估计运行时间最短的进程,将处理机分配给它,使它立即执行并一直执行到完成,或者发生某件事情而被阻塞放弃处理机的时候再重新调度。
3、高优先权调度算法(FPF)
为了照顾紧迫型作业,使之进入系统后获得最优处理,引入了高优先权算法,常用于批处理系统中(作为作业调度算法),使用实时系统(作为进程调度算法),分为以下两种方式:
1)非抢占式优先权算法:
在这种方式下,系统一旦把处理机分配给就绪队列中最高优先级的进程,这个进程便会一直执行下去,直至完成;当因为发生某些事件使进程放弃处理机时,系统方可把进程分配给另一个优先权最高的进程。主要用于批处理系统中,和一些对实时性能不太严格的实行系统中。
2)抢占式优先权算法:
在这种方式下,系统同样是将处理机分配给优先权最高的进程,使之执行,但是在执行期间内,只要出现了另外一个优先权更高的进程,进程调度程序就会立刻停止当前进程的执行,重新将处理机分配给优先权最高的进程。
3)高响应比优先权调度算法
为每个作业引进了动态优先权,使作业的优先级随着等待时间的增加而增加,则长作业在等待一定的时间后就一定能够被分配到处理机。
如果作业的等待时间相同,则要求服务时间越短,优先权越高,因而该算法有利于短作业。
如果要求服务时间相同,等待时间越长,则优先权越高,因而使=是实现了先来先服务。
但对于长作业,作业的优先级随着等待时间的增加而增高
所以高响应比优先权算法,既照顾了短作业 ,又考虑了作业到达的次序,不会使长作业 长期得不到服务。是一种很好 的折衷算法,但是每次进行调度时都要做出响应比的计算,这样增加了系统开销。
4、基于时间片的轮转调度算法
1)时间片轮转法
系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把CPU 分配给队首进程,并令其执行一个时间片。时间片的大小从几ms 到几百ms。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机 分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证就绪队列中的所有进程在一给定的时间内均能获得一时间片的处理机执行时间。换言 之,系统能在给定的时间内响应所有用户的请求。
2)面介绍的各种用作进程调度的算法都有一 定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法 使用。而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。 在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。
  1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执 行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……, 第i+1个队列的时间片要比第i个队列的时间片长一倍。
   2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如 果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未 完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。
  3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为 某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行 的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。
九、task_struct结构体
进程是分配系统资源的单位。当一个程序被加载到内存之后并为他分配一个PCB(进程控制块),这时候就称为进程了。在linux中PCB就是一个名字叫做task_struct的结构体,我们叫他”进程描述符”。它里面有进程执行的所有信息,所以CPU对task_struct进行管理就相当于在对进程进行管理。
PCB叫做进程控制块,它用来维护进程相关的信息,每个进程都有一个PCB。在linux中这个PCB是一个叫做task_struct的结构体。
task_struct包含以下内容:
标示符:描述本进程的唯一标示符,用来区别其他进程。
状态:任务状态,退出代码,退出信号等。
优先级:相对于其他进程的优先级。
程序计数器:程序中即将被执行的下一条指令的地址。
内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
上下文数据:进程执行时处理器的寄存器中的数据。
I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和正在被进程使用的文件列表。
记账信息:可能包括处理器时间总和,使用的时钟总数,时间限制,记账号等。

猜你喜欢

转载自blog.csdn.net/ffsiwei/article/details/80972028