希望打开这篇能对你有帮助。
以下内容为简述。
一段程序的执行过程。
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位(注意和线程区分),是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
为了使程序在多道程序环境下能够并发执行,并对并发执行的程序加以控制和描述,引入进程的概念。 程序段、数据段及进程控制块三部分构成了一个进程的实体。
(1)进程是程序的一次执行,是一个动态的概念,程序是完成某个特定功能的指令的有序序列,是一个静态的概念 (2)一个进程可以执行一个或几个程序,同一程序也可能由多个进程同时执行 (3)进程是系统进行资源分配和调度的一个独立单位,程序则不是 (4)程序可以作为一种软件资源长期保存,而进程是程序的一次执行过程,它是临时的,有生命期的 进程是具有结构的
总结一句话:进程是程序的运行
在进程中,CTRL+C。
终端用户的需要
当终端用户在自己的程序运行期间发现有可疑问题时,往往希望暂时使自己的进程静止下来。
父进程的需要
父进程常常希望考察和修改子进程或者当要协调各子进程间的活动
操作系统的需要
操作系统有时需要挂起某些进程,检查运行中资源的使用情况及进行记账,以便改善系统运行的性能。
对换的需要
为了缓解内存紧张的情况,即将内存中处于阻塞状态的进程换至辅存上,使进程又处于一种有别于阻塞状态的新状态。
负荷调节的需要
-进程控制块记录进程信息
-操作系统是根据进程控制块PCB来对并发执行的进程进行控制和管理的
-PCB是进程存在的唯一标志
-进程标识符信息 进程标识符用于唯一地标识一个进程,通常有外部标识符和内部标识符 ①外部标识符。由创建者提供,通常由字母、数字所组成,往往是由用户(进程)在访问该进程时使用。 ②内部标识符。这是为了方便系统使用而设置的。在所有操作系统中都为每一个进程赋予一个惟一的整数作为内部标识符,它通常就是一个进程的序号。
进程调度就是系统按照某种算法把CPU动态地分配给某一就绪进程。进程调度工作是通过进程调度程序来完成的。 进程调度程序的主要功能: -选择进程占有CPU -进行进程上下文的切换
分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生某事件(如提出I/O请求)而阻塞时才把处理机分配给另一进程 -优点:简单,系统开销小, -缺点:貌似公正,可能导致系统性能的恶化
该方式规定,当一个进程正在运行时,系统可以基于某种原则,剥夺已分配给它的处理机,将之分配给其它进程 剥夺原则:优先权原则、短进程优先原则、时间片原则
-算法:把处理机分配给最先进入就绪队列的进程
-优点:易于实现
-缺点:表面上公平,服务质量不佳 、对短进程不利
-算法:从就绪队列中选出“下一个CPU执行期”最短的进程,为之分配处理机使之执行 -优点:可获得较好的调度性能 -缺点: 进程的CPU执行期难以准确得到、对长进程不利
-算法:响应比=(等待时间+要求的服务时间)/要求的服务时间 ,每次选取响应比最高的进程调度
-优点:所以对短进程有利,并且考虑了等待时间
-缺点:计算响应比有一定的系统开销
-算法:将CPU分配给就绪队列中优先级最高的进程 -静态优先级 在进程创建时确立,确定后运行期间保持不变。确立依据有:进程的类型、进程对资源的需求、用户申请的优先级 优点:简单 缺点:不能动态反映进程特点,系统调度性能差
进程在开始创建时,根据某种原则确定一个优先级后,随着进程执行时间的变化,其优先级不断地进行动态调整
确定依据:根据进程占有的CPU时间的长短来决定,占有时间越长优先级越低;根据进程等待CPU的时间来决定,时间越长优先级越高
算法:通常用在分时系统,它轮流地调度系统中所有就绪进程,使就绪进程依次获得一个时间片的运行时间
-算法:该方法用在批处理和分时相结合的系统中。将分时用户作业放在前台,把批处理作业放在后台。系统对前台作业按照时间片轮转法进行调度,仅当前台无作业时,才把处理机分配给后台作业的进程。后台进程通常按先来先服务方式运行 -优点:使分时用户进程得到及时响应,又提高了系统资源的利用率
-系统设置多个不同优先级的就绪队列,每次调度总是先调度优先级高的队列,仅当该队列空时,才调度次高优先级队列。 -通常刚创建的进程和因请求I/O未用完时间片的进程排在最高优先级队列,在这个队列中运行2-3个时间片未完成的进程排列下一个较低优先级队列。 -不论什么时候,只要较高优先级队列有进程进入,立即转进程调度,及时调度较高优先级队列进程。 -优点:能较好地满足各类作业的用户要求,既能使分时用户作业得到满意的响应,又能使批处理用户的作业获得较合理的周转时间
-进程未用完一个时间片便结束,这时系统应提前进行调度
-进程在执行过程中提出I/O请求而阻塞,系统应将它放入相应的阻塞队列并引起调度
-进程用完一个时间片后尚未完成。系统应将它重新放到就绪队列的末尾,等待下次执行
-正在执行的进程运行完毕
-正在执行的进程调用阻塞原语将自己阻塞起来进入等待状态
-在采用抢占式优先级调度时,有优先级高于正在运行进程的进程进入就绪队列
-在分时系统中时间片已经用完
-CPU方式是可剥夺时,就绪队列中的某个进程 优先级变得高于当前运行进程的优先级
-进程调度所依赖的数据结构通常是调度队列,由于调度的原因不同,在单处理器系统中设置了多种等待队列 -只有就绪队列中的进程能够获得处理器而最终运行,其他队列中的进程从队列中出来后,必须进入就绪队列才能分配处理器 -队列数据结构的建立结构与调度算法密切相关 -进程调度算法只是决定哪一个进程将获得处理机,而将处理机分配给该进程的具体操作是由分派程序完成的
-资源共享关系 -相互合作关系
- 空闲让进
- 忙则等待
- 有限等待
- 让权等待
只要求读的进程称为“reader进程”,其他进程称为“writer进程”。 允许多个reader进程同时读一个共享对象,但决不允许一个writer进程和其他reader进程或writer进程同时访问共享对象 所谓读者-写者问题(The Reader-Writer Problem)是只保证一个writer进程必须与其他进程互斥地访问共享对象的同步问题
了解一下读写锁,思考读写锁带来的阻塞是否是业务所能接受的。
有五个哲学家,他们的生活方式是交替地进行思考和进餐。哲学家们共用一张圆桌,分别坐在周围的五张椅子上。在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐。进餐毕,放下筷子继续思考。
我觉得解决方法已经不重要了,为什么不先反问一句:为什么会出现这种状况?这不应该是设计方面应该考虑的问题吗?
在多道程序系统中,若对资源的管理、分配和使用不当,也会产生一种危险,即在一定条件下会导致系统发生一种随机性错误——死锁(参考上面两个问题)。
哪儿来那么多原因,说白了就是资源分配不均呗。
如果真发生了,那就只有破坏死锁条件一条路可走了。 不要幻想啥 trylock 了,要锁就锁嘛,还要试一下哦。。
可利用资源向量Available:是一个具有m个元素的数组,其中每一个元素代表一类 可利用的资源数目 ,如果Available[j]=k,表示系统中现有Rj类资源有k个
最大需求矩阵Max:是一个n×m的矩阵,它定义了系统中n个进程中的每一个进程,对m类资源的最大需求,如果Max(i,j)=k,表示进程i需要Rj类资源的最大数目为k
分配矩阵Allocation:一个n×m的矩阵,它定义了系统中每一类资源当前已分配给每一个进程的资源数,如果Allocation(i,j)=k,表示进程i当前已分得Rj类资源的数目为k
需求矩阵Need:是一个n×m的矩阵,用以表示每一个进程尚需的各类资源数,如果Need[i,j]=k,表示进程i还需要Rj类资源k个,方能完成其任务 Need(i,j)=Max(i,j)-Allocation(i,j)
使用工具。
资源分配图是由一组结点N和 一组边E所组成的一对偶G =(N,E) 结点分为两种结点:进程结点和资源结点;边表示进程和资源的请求分配关系
-剥夺资源 -撤销进程
#include <unistd.h>
pid_t fork(void);
功能:子进程复制父进程中的0~3g空间和PCB,但ID号不同。
fork调用一次返回两次 父进程中返回子进程id (就是大于0的意思) 子进程返回0 读时共享写时复制,可保高效
与之相关函数:
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void); //获取进程ID
pid_t getppid(void); //获取父进程ID
进程的产生有多种方式,但是追本溯源是相通的。
(1)复制父进程的系统环境(放心,只要是你开的进程,肯定有父进程)
(2)在内核中建立进程结构
(3)将结构插入到进程列表,便于维护
(4)分配资源给该进程
(5)复制父进程的内存映射消息
(6)管理文件描述符和链接点
(7)通知父进程
下面是一张进程列表的图,命令:pstree。
可以看到init是所有进程的父进程,其他进程都是由init进程直接或间接fork出来的。
fork子进程是为了执行新程序(fork创建了子进程后,子进程和父进程同时被OS调度执行,因此子进程可以单独的执行一个程序,这个程序宏观上将会和父进程程序同时进行)
使用exec族函数运行新的可执行程序。exec族函数可以直接把一个编译好的可执行程序直接加载运行。
有了exec族函数后,典型的父子进程程序是这样的:子进程需要运行的程序被单独编写、单独编译链接成一个可执行程序(hello)。主进程为父进程,fork创建了子进程后在子进程中exec来执行hello,达到父子进程分别做不同程序同时(宏观上)运行的效果。
#include<unistd.h>
int execve(const char *path,char *const argv[],char *const envp[]);//这个是真正的系统调用
//以下的函数最后都是调用这个函数
int execl(const char *path,char *const argv,···);
int execlp(const char *file,char *const argv,···);
int execle(const char *path,char *const argv,···· ,char *const envp[]);
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv,);
exec函数族装入并运行程序path/file,并将参数arg0(arg1, arg2, argv[], envp[])传递给子程序,出错返回-1.
看一下后缀:
后缀 | 功能 |
---|---|
l | 希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志 |
v | 希望接收到一个以NULL结尾的字符串数组的指针 |
p | 是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件 |
e | 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境 |
下面我找到一些通俗易懂的栗子,算是让我明白了一点:
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
//以NULL结尾的字符串数组的指针,适合包含v的exec函数参数
char *arg[] = {"ls", "-a", NULL};
/**
* 创建子进程并调用函数execl
* execl 中希望接收以逗号分隔的参数列表,并以NULL指针为结束标志
*/
if( fork() == 0 )
{
// in clild
printf( "1------------execl------------\n" );
if( execl( "/bin/ls", "ls","-a", NULL ) == -1 )
{
perror( "execl error " );
exit(1);
}
}
/**
*创建子进程并调用函数execv
*execv中希望接收一个以NULL结尾的字符串数组的指针
*/
if( fork() == 0 )
{
// in child
printf("2------------execv------------\n");
if( execv( "/bin/ls",arg) < 0)
{
perror("execv error ");
exit(1);
}
}
/**
*创建子进程并调用 execlp
*execlp中
*l希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志
*p是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件
*/
if( fork() == 0 )
{
// in clhild
printf("3------------execlp------------\n");
if( execlp( "ls", "ls", "-a", NULL ) < 0 )
{
perror( "execlp error " );
exit(1);
}
}
/**
*创建子里程并调用execvp
*v 望接收到一个以NULL结尾的字符串数组的指针
*p 是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件
*/
if( fork() == 0 )
{
printf("4------------execvp------------\n");
if( execvp( "ls", arg ) < 0 )
{
perror( "execvp error " );
exit( 1 );
}
}
/**
*创建子进程并调用execle
*l 希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志
*e 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境
*/
if( fork() == 0 )
{
printf("5------------execle------------\n");
if( execle("/bin/ls", "ls", "-a", NULL, NULL) == -1 )
{
perror("execle error ");
exit(1);
}
}
/**
*创建子进程并调用execve
* v 希望接收到一个以NULL结尾的字符串数组的指针
* e 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境
*/
if( fork() == 0 )
{
printf("6------------execve-----------\n");
if( execve( "/bin/ls", arg, NULL ) == 0)
{
perror("execve error ");
exit(1);
}
}
return EXIT_SUCCESS;
}
这里几个概念:
僵尸进程:子进程退出,父进程没有及时回收,子进程成为僵尸进程 孤儿进程:父进程退出,而子进程没有退出,子进程成为孤儿进程 init进程:1号进程,负责收留孤儿进程,成为他们的父进程
有几种方式终止进程:
(1)main返回
(2)调用exit
(3)调用_exit
(4)调用abort(给自己发送异常终止信号)
(5)由一个信号终止
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status);
//这里的status为一个整形指针,是该子进程的返回状态。若该指针不为空,则可以通过该指针获取子进程退出时的状态。
pid_t waitpid(pid_t pid,int *status,int options);
// pid是进程号
/*
<-1 回收指定进程组内的任意子进程
-1 回收任意子进程
0 回收和当前waitpid调用一个组的所有子进程
>0 回收指定ID的子进程
*/
//options:
/*
WNOHANG:强制回收,不阻塞。
WUNTRANCED:一般用上面那个
*/