系统编程:

 进程概念->进程控制->基础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

为什么要创建子进程?意义何在?

  1. 分摊压力,cpu资源足够的情况父子进程同时处理数据,效率高
  2. 希望子进程完成其他的任务

进程状态

普遍的系统的三种状态

就绪,运行,阻塞


Linux进程状态:

  1. 运行态(R)
  2. 可中断睡眠态(S)
  3. 不可中断睡眠态(D)
  4. 停止态(T)
  5. 僵死态(Z)
  6. 死亡态(X)
  7. 追踪态(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++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但 是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

就是内存解释
和环境变量相关的命令

  1. echo: 显示某个环境变量值
  2. export: 设置一个新的环境变量
  3. env: 显示所有环境变量
  4. unset: 清除环境变量
  5. 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规则进行排队调度,所以,数组下 标就是优先级!
从该结构中,选择一个最合适的进程,过程是怎么的呢?

  1. 从0下表开始遍历queue[140]
  2. 找到第一个非空队列,该队列必定为优先级最高的队列
  3. 拿到选中队列的第一个进程,开始运行,调度完成!
  4. 遍历queue[140]时间复杂度是常数!但还是太低效了!
    bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个 比特位表示队列是否为空,这样,便可以大大提高查找效率
    过期队列
过期队列和活动队列结构一模一样过期队列上放置的进程,都是时间片耗尽的进程 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算

active指针和expired指针

  1. active指针永远指向活动队列

  2. expired指针永远指向过期队列

  3. 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在 的。

  4. 没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活 动进程!

在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不随着进程增多而导致时间成本增 加,我们称之为进程调度O(1)算法

linux系统编程之进程概念(操作系统---管理,进程创建,进程状态,进程优先级, 环境变量,程序地址空间,进程O(1)调度方法)相关推荐

  1. linux系统编程界面实验报告,操作系统实验报告-Linux操作使用编程.doc

    操作系统实验报告-Linux操作使用编程 实 验 报 告( 2012/ 2013 学年 第二学期) 课程名称操 作 系 统A实验名称Linux操作.使用.编程实验时间2013年 5 月 6日指导单位计 ...

  2. Linux系统编程——冯诺依曼体系结构,操作系统

    一.冯诺依曼体系 冯诺依曼是一位著名的数学家,他主要研究博弈论,但他在计算机学科有着重要的贡献,其中一个就是提出了冯诺依曼体系. 冯诺依曼体系为计算机的构建提供了很好的模板, 现如今所有的计算机都遵循 ...

  3. Linux 系统编程技巧与概念 第12章 基于 TLV 传输

    TLV Type Length Value 机制 传统数据传输,不同情况下会容易做成数据收发错误 例如:不同系统 32 位元系统,16 位元系统 之间沟通,由于系统对 unsigned int 解释是 ...

  4. Linux 系统编程技巧与概念 第14章 字节次序

    字节次序 e.g. unsigned short int a = 1; 会用 2 bytes 表示 HEX 0 1 BIN 00000000 00000001 Big Endian 表示方式 内存地址 ...

  5. Linux 系统编程技巧与概念 第11章 不透明指针

    不透明指针 Opaque Pointers 用来防止用户重复或不正确实现库提供的功能 所以属性皆为私有 现实常用 实现例子: 重点 ll_node_ 不对于,这样外面就不能直接操作,例如 sizeof ...

  6. 嵌入式Linux系统编程学习之二十七线程的创建和退出

      创建线程实际上就是确定调用该线程函数的入口点,线程的创建采用函数 pthread_create .在线程创建以后,就开始运行相关的线程函数,在该函数运行完之后,线程就退出,这也是线程退出的一种方式 ...

  7. Linux系统编程38:多线程之什么是线程同步以及条件变量函数

    文章目录 (1):什么是线程的同步 (2):实现线程同步-条件变量函数 (1):什么是线程的同步 假如有一片临界资源,线程A和B都会修改它,为了保护资源所以要加锁,此时它们之间是互斥的关系.在我们的代 ...

  8. 【Linux | 系统编程】Linux系统编程(文件、进程线程、进程间通信)

    文章目录 Linux系统编程 文件IO open/close函数 read/write函数 文件描述符 阻塞.非阻塞 fcntl函数 lseek函数 传入传出参数 文件系统 文件存储 文件操作 sta ...

  9. Linux系统编程(二)孤儿进程和僵尸进程

    Linux系统编程(二) 一.exec函数族 1.exec函数 二.孤儿进程和僵尸进程 三.wait和waitpid 1.wait函数 2.waitpid函数 一.exec函数族 exec函数使用时, ...

  10. Linux系统编程总结

    day2 vim的三种工作模式 命令模式 vi hello.c zz 保存退出 2.编辑模式 i a o s (有大写)可以写东西 3.末行模式: 文本和末行模式不能直接切换 要切换回命令模式 再到末 ...

最新文章

  1. 史上最全,最完美的 JAVA 技术体系思维导图总结,没有之一!
  2. python土木_土木和结构工程师用Python-Python for civil and structural engineers
  3. vaniglia 源码学习 (六)
  4. 【SSL协议】SSL协议详解
  5. leetcode346. 数据流中的移动平均值
  6. 人口、人口密度分析项目-条形图
  7. 大数据平台搭建包含哪些层级
  8. 住在我隔壁储藏室的大学刚毕业的小夫妻[转载]
  9. Unity-TouchScripts中使用TUIO的记录和简单的代码分析
  10. 计算机系统保密检查整改情况函,民政局保密工作整改情况的报告
  11. redis 完整使用文档(2021)
  12. java线程游戏之随机小球游戏V1
  13. linux 动态监控进程
  14. 浅析微服务架构应该在什么时候采用
  15. 思维导图带你看遍花样百出的各类月饼?
  16. 格式化硬盘出现 is apparently in use by the system错误
  17. usb接口驱动_win10系统USB接口没反应怎么解决
  18. 苹果开发者平台-《测试证书》生成流程
  19. 复函数积分的共轭等于其共轭的积分
  20. mysql怎么判断是否断号_孩子是否有多动症,应该怎么判断呢?

热门文章

  1. 转:Google论文之三----MapReduce
  2. nginx资源定向 css js路径问题
  3. [转]深入浅出Java设计模式之备忘录模式
  4. rust为什么显示不了国服_Rust编程语言初探
  5. linux制作一键恢复,Linux/Centos Mondo 一键部署、镜像恢复,快速部署
  6. java中u怎么用_Java中interrupt的使用
  7. linux rsync删文件速度,为什么用rsync删除大量文件的时候比用rm快
  8. 计算机科学家和心理学家合作,生物智能与人工智能之间的合作
  9. python菜鸟教程split_Python split()方法
  10. Finally语句块的执行