问题的引入
- 程序 = 数据结构 + 算法
- 数据:用来表示人们对思维对象的抽象概念的物理表现叫做数据
- 数据处理的规则叫做操作(指令)
- 对某个有限数据集合所施行的,目的在于解决某个问题的一组有限的指令的集合,称为这一个计算(compute)
- 计算机就是用指令来处理数据,程序就是数据和指令的集合,一个程序的执行过程就是一个计算
程序的执行方式
顺序执行
一个程序完全执行完毕之后,才能执行下一个程序。这样子的执行方法CPU利用率很低!!!
并发执行
- A.并发运行就是让计算机同时运行几个程序或同时运行同一程序多个进程或线程。
- B.早期的计算机只具有一个中央处理器(CPU)并且是单核(只有一个运算器)的,这种情况下计算机操作系统采用并发技术实现并发运行,具体做法是采用“ 时间片轮询进程调度算法”,它的思想简单介绍如下: 在操作系统的管理下,所有正在运行的进程轮流使用CPU,每个进程允许占用CPU的时间非常短(比如10毫秒),这样用户根本感觉不出来CPU是在轮流为多个进程服务,就好象所有的进程都在不间断地运行一样。但实际上在任何一个时间内有且仅有一个进程占有CPU及CPU的运算器。
- C.现阶段许多计算机具有多个中央处理器或一个处理器具有多个运算器(多核),情况就不同了,如果进程数小于CPU或运算器数,则不同的进程可以分配给不同的CPU或运算器来运行,这样,各个进程就是真正同时运行的,这便是并行。但如果进程数大于CPU或运算器数,则仍然需要使用并发技术。
- D.有些操作系统并不支持多个CPU或多核CPU,如 ms winodws 9x、3.x,这样的操作系统多个CPU、或多核CPU对它们来说是无用的。
现代操作系统为了能让程序并发执行,特地引入“进程”的概念
进程是什么东西???
- 狭义定义:进程就是一段程序的执行过程。
- 广义定义:进程是一个具有一定独立功能的程序关于某个数据 集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程有怎么样的特征???
- 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的;
- 并发性:任何进程都可以同其他进程一起并发执行;
- 独立性:进程是一个独立运行的基本单位,同时也是系统分配资源和调度的对立单位;
- 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各种独立的,不可预知的速度向前推进;
- 结构特点:进程由程序,数据和进程控制块三部分组成;
多个不同的进程可以包含相同的程序;一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变
Linux进程结构
- Linux进程结构:可由三部分组成:代码块、数据段、堆栈段。也就是程序、数据、进程控制块PCB(process control block)组成。进程控制块是进程存在的唯一标识,系统通过PCB的存在而感知进程的存在。
- 系统通过PCB对进程进行管理和调度。PCB包括创建进程、执行进程、退出进程以及改变进程堆优先级等。而进程中的PCB用一个名为task-struct的结构体表示(task-struct下面注释解释是什么东西,我刚开始学的时候,也看到这些名词一脸懵),定义在include/linux/sched.h(linux内核源码头文件)中,每当创建一新进程时,便在内存中申请一个空的task-struct的结构,填入所需要信息,同时,指向该结构的指针耶被放入到task数组中,所有进程控制块都存储在task[]数组中。
- 标识符:表示一个进程的唯一标识,来区别其他进程
- 状态:任务状态,退出代码,退出信号等
- 并行:推进代码,多个同时进行
- 优先级:(这个就与权限有关了)
- 硬件上下文:cpu在切换进程之前保存当前进程多信息,下次加载该进程的时候,又可以回到上次的状态,该休息保存在PCB里面,可因此PCB里面,可因PCB里面应该保存硬件上下文信息(就是保存运行状态)
- I/O状态:包括处理器时间的总和,使用时钟总和等
- 内存指针:程序代码和进程相关数据的信息指针,还有和其他进程共享的内存块的指针,找到目标程序的代码和数据
其实我也没有搞的很清楚,有小伙伴可以补充的可以补充哦
进程和程序的区别
- 程序是静态概念(是指令的有序集合,“程序文件”);进程是动态的概念(动态产生,动态消亡);
- 进程是一个程序的一次执行活动;一个程序可以对应多个进程
- 进程是一个独立的活动单位;进程是竞争系统资源的基本单位。
Os为什么要引入进程呢? 就是为了能让程序并发执行(同一时段可以有多个程序在运行)
进程状态
进程的几种状态
- Os把进程的执行过程,分为几个不同的阶段(状态):
- 就绪状态。某些进程“万事俱备”(必要资源),只差CPU。(就绪队列)
- 执行状态。某进程占有CPU并在CPU上执行其程序。
- 阻塞状态。某些进程由于某种原因不能继续运行下去,等待处 理问题。也称为等待状态或封锁状态。如:请求I/O。(多个等待队列)
状态的转换
- 就绪-→执行:
- 对就绪状态的进程,当进程调度程序按一种选定的策略从中选中一个就绪进程,为之分配了处理机后,该进程便由就绪状态变为执行状态;
- 执行-→阻塞:
- 正在执行的进程因发生某等待事件而无法执行,则进程由执行状态变为阻塞状态。 如:进程提出输入/输出请求而变成等待外部设备传输信息的状态,进程申请资源(主存空间或外部设备)得不到满足时变成等待资源状态,进程运行中出现了故障(程序出错或主存储器读写错等)变成等待干预状态等等;
- 阻塞-→就绪:
- 处于阻塞状态的进程,在其等待的事件已经完成,如输入/输出完成,资源得到满足或错误处理完毕时,处于等待状态的进程并不马上转入执行状态,而是先转入就绪状态,然后再由系统进程调度程序在适当的时候将该进程转为执行状态;
- 执行-→就绪:
- 正在执行的进程,因时间片用完而被暂停执行,或在采用抢先式优先级调度算法的系统中,当有更高优先级的进程要运行而被迫让出处理机时,该进程便由执行状态转变为就绪状态。
linux进程地址空间布局
- “分段”:分不同的逻辑区域,linux对进程的数据进行分段管理,不同的属性的数据,存储在不同的“内存段”中。不同的“内存区域”的属性及管理方法不一样。
.text
- 主要存放代码。只读并且共享,这段内存存在程序运行期间(进程存活期间),不会释放的。“代码段”随程序持续性(随进程持续性)
.data
- 数据段。主要存放程序的已经初始化的全局变量和已经初始化的static变量。可读可写,这段内存存在进程运行期间,一直存在。随进行持续性
.bss
- 数据段。主要存放程序没有初始化的全局变量和没有初始化的static变量。可读可写,这段内存在进程运行期间,一直存在。随进行持续性。.bss段,在进程初始化时,可能全部初始化为0。
.rodata
- 只读数据段。主要存放程序的只读数据(如:字符串常量)。只读,这段内存存在进程运行期间,一直存在。随进行持续性。
栈空间(stack)
- 主要是存放局部变量(非static的局部变量)可读可写。这段空间,会自动释放(代码块执行完了,代码块的局部变量的空间就会自动释放)。随代码块持续性。
堆空间(heap)
- 动态内存空间,主要malloc/realloc/calloc动态分配的空间。可读可写的,这段内存存在运行起劲啊,一旦分配,就会一直存在,直到你手动free合进程消亡。防止“内存泄露”/“内存垃圾”。
linux下进程相关的API函数
创建一个新进程(fork)
-
fork
- fork用来创建一个新进程(child process),你要创建一个新进程,首先你得知道一个进程里面包含什么东西?--系统数据--用户数据--指令
- fork一个新进程时,这个新进程的 数据 和 指令 来源于哪里呢?----来源于它的爸爸(父进程,调用fork的那个进程)
- fork这个函数在创建子进程时:copy了父亲的数据和指令!!!!(父进程的变量,数据对象,标准IO缓冲区,文件描述符),copy完了之后,父子进程就独立啦。
- fork成功时,就会有两个进程在执行当前的代码!!!
- 为了区分手父进程还是子进程,fork调用一次有两次返回--一个父进程返回,一个子进程返回
- 通过fork的不同返回值,来区分到底是父进程返回还是子进程返回。
-
大家可以通过在终端 man(男人一下,哈哈哈)[man fork]可以看到fork函数的使用方法
fork的实现的伪代码:
pid_t fork()
{
clone(); //克隆,一旦成功,就会有两个进程往下执行
// 父进程, 子进程
if (父进程)
{
return 子进程的pid(>0) ;
}
else if (子进程)
{
return 0;
}
}
代码实现:
获取进程ip
- linux系统会为每一个进程,分配一个唯一的进程id(>0的整数),用类型pid_t来描述,而且还提供两个函数用于获取当前进程(自己)以及父进程的pid:
pid_t getpid()
pid_t gettpid()
进程退出
进程退出有两种情况:
自杀(自己退出)
- main函数返回,进程退出
- 在进程执行的任意调用进程退出函数(exit/_exit)
例子
int main()
{
printf("hello world");
_exit(-3);
}
int main()
{
printf("hello world");
exit(-3);
}
//分析这个程序的输出结果
//答案:
被操作系统干掉
等待子进程退出
- 头文件
- #include <sys/types.h>
- #include <sys/wait.h>
- pid_t wait(int *status);
- pid_t waitpid(pid_t pid, int *status, int options);
pid_t wait(int *status)
-
用来等待任意一个子进程退出或状态发生改变的。 pid_t wait(int *status);
- status:指针。指向的空间,用来保存子进程的退出信息的(怎么死的,退出码等等)
- 返回值:成功返回退出的那个子进程的进程id;失败返回-1,同时errno被设置。
pid_t waitpid(pid_t pid, int *status, int options);
-
pid: 指定要等待的进程或进程组
- pid == -1 ,表示等待任意的子进程退出,
- pid == 0 ,表示等待与调用进程同组的任意子进程。
- pid < -1 表示等待组id等于 pid绝对值的那个组的任意子进程
- pid > 0, 表示等待指定的子进程(其进程id为pid的那个子进程)
-
status 同下
-
options: 等待选项
- 0: 表示阻塞等待
- WNOHANG: 非阻塞,假如没有子进程退出,则立即返回。
-
返回值
- 成功返回退出的那个子进程的进程id
- 失败返回-1,同时errno被设置。
status
例子:wait(&status)<=> waitpid(-1, &status, 0)
- fork 一个子进程,一般来说,是让子进程去执行其他的任务
exec函数族
- exec函数族 主要作用是 让一个进程去执行 一个指定的程序文件。或者这么理解: exec函数族的作用就是让一个指定的程序文件中的数据和指令替换到 调用进程 的数据和指令
- exec函数族是让一个进程去执行另外一个程序文件。就是让另外一个程序文件到数据的指令,覆盖当前进程的数据和指令。
- exec让一个进程去执行另外一个程序,那么:
- 你是不是要指定这个程序文件的名字?
- 一个在文件系统中的程序文件的名字(带路径)
- 你可能还的制定程序运行的参数!!!
-
在linux下程序的参数,都是字符串。
-
指定程序的参数有两种方式:
l:list 把程序运行的参数,一个一个地列举出来;程序运行的第一个参数,是程序的名字[不带路""径]------>"sum_zl","3","4",NULL
v:vector向量,数组 把程序运行的参数,弄成一个char * 的数组 char* argv[]={"sum_zl","3","4",NULL};
-
- 你是不是要指定这个程序文件的名字?
execl
- execl:让进程去执行参数指定的程序文件。
- int execl(const char *path , const char *arg,.....);
- path:程序文件的文件名(带路径的)。
- arg,...:程序运行的参数,l->list------如:"sum_zl","3","4",NULL
- 返回值:
- 失败返回-1,同时errno被设置。
- 成功,就永远不会返回啦!!!因为你的整个指令和数据段,都被人家替换掉啦。还怎么返回呢?
execv
- execv和execl作用、功能、返回值都一样、唯一不同的是,指定的程序文件的参数方式不一样。
- int execv(const char *path , char * const argv[]);
- path:要执行的程序文件名(带路径)
- argv[]:指定程序运行的参数。程序运行的第一个参数是程序名,最后一个为NULL,表示参数结束了。
- 系统中有环境变量 PATH
- 环境变量:是整个系统环境内所有进程共享的变量。有很多的环境变量,其中有一个环境变量叫PATH---PATH:=dir1:dir2:dir3:...PATH的作用是,指定命令或程序的搜索路径。什么意思呢?当你只指定一个命令或程序的文件名,而没有指定路径时,那么系统首先会在dir1这个目录下去查找,如果找到,则执行,没有找到,则在dir2这个目录下去查找。意思也就是说,如果你的程序文件或命令已经在PATH指定的搜索目录下的时候,你指定文件时就没有必要指定路径啦。
execlp
execvp
system
- system用来执行command指定的命令或程序,system会等待命令或程序执行完成。
- int ststem