系统编程:
	进程概念->进程控制->基础IO->进程间通信->进程信号->多线程
 
进程概念
冯诺依曼体系结构----现代计算机硬件体系结构

 冯诺依曼体系结构----现代计算机硬件体系结构
	计算机五大硬件单元:输入设备:键盘输出设备:显示器存储器:内存-外存---固态接口类型SATA   SATA3  PCI-E(目前最好)运算器:CPU—主频2.5GHz,主频越大代表时钟震荡周期越高,代表1s中处理的指令也就越多控制器:CPU所有设备都是围绕存储器工作的
 
关于冯诺依曼,必须强调几点:
- 这里的存储器指的是内存 不考虑缓存情况,
 - 这里的CPU能且只能对内存进行读写,
 - 不能访问外设(输入或输出设备) 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
 - 一句话,所有设备都只能直接和内存打交道。
 
对冯诺依曼的理解,不能停留在概念上,要深入到对软件数据流理解上,请解释,从你登录上qq开始和某位朋友聊 天开始,数据的流动过程。从你打开窗口,开始给他发消息,到他的到消息之后的数据流动过程。如果是在qq上发 送文件呢?
硬件决定了软件的行为
操作系统—管理

 操作系统:
一个软件安装在计算机硬件上
 
目的:
为了让计算机更加好用—功能:合理统筹管理计算机上边的软硬件资源
 
管理:
先描述使用pcb描述进程,使用双向链表将pcb串起来进行管理,再组织。
 
库函数与系统调用接口的关系:
封装关系:库函数封装了系统调用接口,是上下级的调用关系
 
进程概念----进程是什么
进行中的程序
 linux是一个多任务操作系统,表示有大量的程序需要被cpu调度运行,这时候cpu使用了分时技术,分别轮询处理每一个进程,在进程程序切换调度时,需要记录运行信息,因此操作系统在调度进程在cpu上运行时,使用pcb对运行中的程序进行描述,通过调度pcb完成对进程的调度,因此进程是pcb。
 pcb对运行中程序进行描述
 每一个运行的程序都是pcb
在操作系统角度,操作系统通过pcb来控制一个进程的运行,这个pcb也叫进程描述符,描述了一个运行中的程序
 Linux操作系统下的PCB是task_struct结构体(用双向链表进行组织的)
什么时task_struct结构体
参考链接
task_struct结构体中的内容
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
 - 状态: 任务状态,退出代码,退出信号等。
 - 优先级: 相对于其他进程的优先级。
 - 程序计数器: 程序中即将被执行的下一条指令的地址。
 - 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
 - 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
 - I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
 - 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
 - 其他信息
 
内存指针
pcb中有一个指针指向了当前要运行的程序
cpu通过pcb内存指针知道代码在什么位置,然后加载到内存上面
 
cpu分时机制
不会体会到卡顿的原因,调度进程时,不会一直在一个进程上面运行,轮询调度pcb 。
每个都执行一段时间,切换速度很快。
每个进程只运行很短的一段时间(时间片)
 
程序计数器
即将执行的指令的地址
 
上下文数据
cpu正在处理的数据是什么
 
标识符PID
每一个进程都有一个ID
 
进程状态
当前进程处于什么状态
 
优先级
前台进程(交互式进程)优先级更高
批处理(后台进程)
 
IO状态信息
每一个进程里面都会打开很多的文件,打开文件就要进行管理
记录描述文件,所以需要保存下来这些信息
 
记账信息
一个进程大致在cpu上运行了多长时间
 
进程查看
ps
-ef 
-aux		查看系统所有进程信息
 
- /proc 保存系统中正在运行的程序信息
 - pid_t getpid() 获取调用进程的pid

 - 根目录下的proc/目录存放的就是当前操作系统上面正在运行中的程序的运行信息
 
例如
#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
int main()    
{    while(1){    sleep(1);        }    return 0;       
}   
 

通过系统调用获取进程标示符
#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
int main()    
{    printf("pid: %d\n", getpid());    printf("ppid: %d\n", getppid());    return 0;    
}   
 

进程创建
创建进程就是创建pcb
 
 用fork创建进程
 fork()—通过复制调用进程(父进程)创建一个新的进程(子进程)
 子进程与父进程完全相同
 
 
 head line 打印了一次
 tail line 打印了两次
 
 复制了父进程的pcb(意味着和父进程拥有一样的内存指针,程序计数器,上下文数据):
 和父进程运行相同的代码,相同的运行位置,
 处理一样的数据
父子进程代码共享,数据独有 。
 同一个内存区域,打印的值相同
子进程创建成功都是从下一步指令开始运行
如何分辨父子进程:通过返回值
 
 父子进程不一定谁先运行,要看cpu调哪个pcb
 父进程:
	返回子进程的pid,pid>0
 
子进程:
返回0
 
失败:
返回-1
 
为什么要创建子进程?意义何在?
- 分摊压力,cpu资源足够的情况父子进程同时处理数据,效率高
 - 希望子进程完成其他的任务
 
进程状态
普遍的系统的三种状态
就绪,运行,阻塞
 

 Linux进程状态:
- 运行态(R)
 - 可中断睡眠态(S)
 - 不可中断睡眠态(D)
 - 停止态(T)
 - 僵死态(Z)
 - 死亡态(X)
 - 追踪态(t)
 

 加号代表前台进程
cpu使用率非常高,什么原因?
死循环。
杀死进程
kill  进程ID
 
普通杀死进程杀不死停止态进程
 
 要用强杀
kill -9 进程ID
 

 僵尸进程:
 处于僵死态的进程----进程退出后,资源没有完全释放(没有完全退出)
 强杀都杀不死
 如何产生?
 
 子进程先于父进程退出,将自己退出原因保存在pcb中,操作系统检测到子进程退出,因为父进程有可能关注退出原因,所以不敢随意释放所有资源,通知父进程子进程的退出,但是这时父进程可能正在打麻将,没有关注到这个通知,导致子进程退出了
 但是资源一直没有 释放,处于僵尸进程,处于僵死状态,成为僵尸进程。
危害:资源泄露,一个用户能够创建的进程是有限的,导致新进程创建失败
 处理:干掉父进程
 如何避免:
进程等待
 
孤儿进程:
父进程先于子进程退出,子进程成为孤儿进程,运行在操作系统后台,父进程成为1号进程(被领养)
 
孤儿进程的使命就是不断奋斗最后成为守护进程
 守护进程/精灵进程
特殊的孤儿进程     一个特殊的孤儿进程(脱离终端,脱离登会话的孤儿进程)
 
进程优先级
通过一个评级来决定一个进程的cpu资源优先分配权
 为了让计算机运行的更加合理
 (因为进程的性质各有不同—批处理/交互式)
 查看:
ps  -l
 
修改:优先级无法直接修改,但是可以通过修改NI的值,来调整PRI的值
 PRI=PRI+NI
 renice程序运行后修改 (nice的范围(-20~19))
	Renic  -n ni_val  -p  pid
 
nice程序运行时指定
	nice  -n  ni_val  ./main
 
优先级调整更多的是针对cpu密集型程序(对cpu资源要求比较高)
 磁盘密集型程序因为本事呢对cpu资源要求不是很高,因此大多数情况下,没必要调整

 我们很容易注意到其中的几个重要信息,有下:
UID : 代表执行者的身份 
PID : 代表这个进程的代号PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号PRI :代表这个进程可被执行的优先级,其值越小越早被执行 NI :代表这个进程的nice值 
 
PRI and NI
PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小 进程的优先级别越高那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值 PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行 所以,调整进程优先级,在Linux下,就是调整进程nice值 nice其取值范围是-20至19,一共40个级别需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进 程的优先级变化。可以理解nice值是进程优先级的修正修正数据
 
用top命令更改已存在进程的nice
top 进入top后按“r”–>输入进程PID–>输入nice的值
 
竞争性:
 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高 效完成任务,更合理竞争相关资源,便具有了优先级
 独立性:
 多进程运行,需要独享各种资源,多进程运行期间互不干扰
 并行:
 多个进程在多个CPU下分别,同时进行运行,这称之为并行
 并发:
 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为 并发
环境变量
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但 是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性 
 
就是内存解释
 和环境变量相关的命令
- echo: 显示某个环境变量值
 - export: 设置一个新的环境变量
 - env: 显示所有环境变量
 - unset: 清除环境变量
 - set: 显示本地定义的shell变量和环境变量
 
环境变量的组织方式
 
 每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串
 常见环境变量:HOME SHLL PATH
通过第三方变量environ获取
 **int argc 参数个数
 char argv[] 字符串指针数组放的是参数
 char env[] 这个字符串指针数组所保存的就是环境变量
#include <stdio.h>int main(int argc, char *argv[], char *env[]){    int i = 0;   for(; env[i]; i++){        printf("%s\n", env[i]);    }    return 0; }
 
通过系统调用获取或设置环境变量
#include <stdio.h> #include <stdlib.h>int main() 
{    printf("%s\n", getenv("PATH"));    
return 0; }
 
环境变量通常是具有全局属性的
 环境变量通常具有全局属性,可以被子进程继承下去
#include <stdio.h> #include <stdlib.h>
int main() 
{  char * env = getenv("MYENV"); if(env){       printf("%s\n", env);  }    return 0;}
 
直接查看,发现没有结果,说明该环境变量根本不存在
 导出环境变量 export MYENV=“hello world”
 再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!想想为什么?
子进程崩溃了,对shell本身没有影响
 
程序地址空间
为什么要用虚拟地址空间+页表:保持进程独立性+充分利用内存+内存访问控制
 段页式内存管理:段号+段内地址+页内偏移
 段式内存管理:段号+段内地址
 页式内存管理:页号+页内偏移
 
 #include <stdio.h> #include <unistd.h> #include <stdlib.h>
int g_val = 0;
int main(){    pid_t id = fork();    if(id < 0){        perror("fork");      return 0;   }else if(id == 0){ //childprintf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);   }else{ //parent       printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);   }  sleep(1);  return 0; }
 

 我们发现,输出出来的变量值和地址是一模一样的,很好理解呀,因为子进程按照父进程为模版,父子并没有对变 量进行进行任何修改。可是将代码稍加改动:
#include <stdio.h>#include <unistd.h>#include <stdlib.h>int g_val = 0;int main() 
{    
pid_t id = fork(); if(id < 0){        perror("fork");      return 0;    }    else if(id == 0){ //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取       g_val=100;       printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);   }else{ //parent       sleep(3);        printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);   }    sleep(1);   return 0; }
 

 我们发现,父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论
变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
但地址值是一样的,说明,该地址绝对不是物理地址!在Linux地址下,这种地址叫做 虚拟地址 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理 
 
OS必须负责将 虚拟地址 转化成 物理地址 。
 地址:内存区域的编号
 -----进程的虚拟地址空间—内存描述符----mm_struct
操作系统通过mm_struct这个结构体给进程描述了一个虚拟的地址
 如何描述:
 mm_struct{
 ulong size;
 ulong code_start;
 ulong code_end;
 ulong data_start;
 ulong data_end;
 }
 
 为什么要使用虚拟地址空间虚拟地址空间+页表
 通过页表进行映射,页表可以进行标记,当前地址是可读还是可写
 提高内存利用率
 对内存访问进行控制
 保证进程独立性
虚拟内存的方式
 写时拷贝技术:提高子进程创建效率
父进程创建了子进程,但是并没有直接给子进程开辟内存,拷贝数据,
而是跟父进程映射到同一位置,
但是如果内存中数据发生的改变,那么对于改变的这块内存,
需要重新给子进程开辟内存,并且更新页表信息。
 
进程O(1)调度方法
一个CPU拥有一个runqueue
普通优先级:100~139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!实时优先级:0~99(不关心) 
 
活动队列
 时间片还没有结束的所有进程都按照优先级放在该队列 nr_active: 总共有多少个运行状态的进程
queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下 标就是优先级!
 从该结构中,选择一个最合适的进程,过程是怎么的呢?
- 从0下表开始遍历queue[140]
 - 找到第一个非空队列,该队列必定为优先级最高的队列
 - 拿到选中队列的第一个进程,开始运行,调度完成!
 - 遍历queue[140]时间复杂度是常数!但还是太低效了!
bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个 比特位表示队列是否为空,这样,便可以大大提高查找效率
过期队列 
过期队列和活动队列结构一模一样过期队列上放置的进程,都是时间片耗尽的进程 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算
 
active指针和expired指针
-  
active指针永远指向活动队列
 -  
expired指针永远指向过期队列
 -  
可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在 的。
 -  
没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活 动进程!
 
在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不随着进程增多而导致时间成本增 加,我们称之为进程调度O(1)算法