第六章 Linux内核.

Slides:



Advertisements
Similar presentations
高级服务器设计和实现 1 —— 基础与进阶 余锋
Advertisements

阻塞操作. 在 linux 里,一个等待队列由一个 wait_queue_head_t 类型的结构来描述 等待队列的初始化: static wait_queue_head_t testqueue; init_waitqueue_head(&testqueue);
© 2001 孟静制作 版权所有 第二章 CPU 管理和进程、线程管理 2.1 CPU 管理概述 2.2 进程管理 2.3 进程模型实例分析 :UNIX 早期版本的 CPU 管理 子系统 ( 进程模型 ) 2.4 处理机管理实例分析 (2):linux CPU 管理(进程 模型) 2.5 线程模型.
1 张惠娟 副教授 Linux 进程管理. 2 内容 进程组成 进程环境 进程管理内容 进程控制块 进程状态 进程调度 进程控制 进程通信.
Linux 系统. 操作系统发展需求 1 没有操作系统 2 简单批处理操作系统 3 多道程序设计的批处理 4 多道程序设计的分时操作系统 5 多处理机并行系统 6 网络操作系统 7 分布式操作系统.
LSF系统介绍 张焕杰 中国科学技术大学网络信息中心
UNIX的程序與工作控制 程序的定義與特性 程序的建立 相關指令 背景執行 調整程序優先權 signal與kill 工作控制
Oracle数据库 Oracle 子程序.
在PHP和MYSQL中实现完美的中文显示
陈香兰 助教:陈博、李春华 Spring 2009 嵌入式操作系统 陈香兰 助教:陈博、李春华 Spring 2009.
Kvm异步缺页中断 浙江大学计算机体系结构实验室 徐浩.
LSF系统介绍 张焕杰 中国科学技术大学网络信息中心
chapter 1-Introduction
OpenMP简介和开发教程 广州创龙电子科技有限公司
第7章 Linux环境编程.
进程及进程管理 第4章 进程及进程管理.
实践演练 广州创龙电子科技有限公司 01 广州创龙电子科技有限公司
走进编程 程序的顺序结构(二).
辅导课程六.
网络常用常用命令 课件制作人:谢希仁.
临界区软件互斥软件实现算法.
Process management(程序管理)
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
文件读写实践 广州创龙电子科技有限公司 01 广州创龙电子科技有限公司
Linux操作系统分析 中国科学技术大学计算机系 陈香兰(0512- )
第十章 IDL访问数据库 10.1 数据库与数据库访问 1、数据库 数据库中数据的组织由低到高分为四级:字段、记录、表、数据库四种。
本节内容 模拟线程切换 视频提供:昆山滴水信息技术有限公司 官网地址: 论坛地址: QQ交流 :
如何生成设备节点 广州创龙电子科技有限公司
临界区软件互斥软件实现算法 主讲教师:夏莹杰
中国科学技术大学计算机系 陈香兰(0551- ) Spring 2009
实验一、进程控制 一、实验目的 1、加深对进程的理解,进一步认识并发执行的实质; 2、分析进程争用资源现象,学习解决进程互斥的方法;
宁波市高校慕课联盟课程 与 进行交互 Linux 系统管理.
宁波市高校慕课联盟课程 与 进行交互 Linux 系统管理.
Linux Debugging ls,ps,top,etc. printk() /proc i386提供的调试机制(断点,信号,单步执行)
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
第二章 登录UNIX操作系统.
内容摘要 ■ 课程概述 ■ 教学安排 ■ 什么是操作系统? ■ 为什么学习操作系统? ■ 如何学习操作系统? ■ 操作系统实例
C语言程序设计 主讲教师:陆幼利.
简单介绍 用C++实现简单的模板数据结构 ArrayList(数组, 类似std::vector)
$9 泛型基础.
VB与Access数据库的连接.
进程概念.
3.5 线程 问题的提出 进程的引入使操作系统得以完成对并发执行的多道程序动态特征的描述和资源共享的管理,因而进程既是调度的基本单位又是资源分配的基本单位。进程所具有的这两个特点构成了程序并发执行的基础,但同时又导致进程切换过程中由于进程映像过大而带来的时空开销。因此,如果系统中创建的进程过多,或进程切换的频率过高,则会使系统效率下降,限制了并发度的进一步提高。
<编程达人入门课程> 本节内容 内存的使用 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群: ,
本节内容 Win32 API中的宽字符 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
信号量(Semaphore).
第4章 Excel电子表格制作软件 4.4 函数(一).
iSIGHT 基本培训 使用 Excel的栅栏问题
§6.7 子空间的直和 一、直和的定义 二、直和的判定 三、多个子空间的直和.
Chapter 18 使用GRASP的对象设计示例.
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
2017 Operating Systems 作業系統實習 助教:陳主恩、林欣穎 實驗室:720A Lab7.
临界区问题的硬件指令解决方案 (Synchronization Hardware)
本节内容 C语言的汇编表示 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
Python 环境搭建 基于Anaconda和VSCode.
本节内容 Windows线程切换_时钟中断切换 视频提供:昆山滴水信息技术有限公司 官网地址: 论坛地址: QQ交流 :
WSAAsyncSelect 模型 本节内容 视频提供:昆山爱达人信息技术有限公司 视频录制:yang
Computer Science & Information Management
实验二:添加Linux系统调用及熟悉常见系统调用
2017 Operating Systems 作業系統實習 助教:陳主恩、林欣穎 實驗室:720A Lab4.
基于列存储的RDF数据管理 朱敏
C++语言程序设计 C++语言程序设计 第一章 C++语言概述 第十一组 C++语言程序设计.
本节内容 动态链接库 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
本节内容 进程 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
第四章 UNIX文件系统.
第十七讲 密码执行(1).
使用Fragment 本讲大纲: 1、创建Fragment 2、在Activity中添加Fragment
本节内容 SEMAPHORE 视频提供:昆山滴水信息技术有限公司 官网地址: 论坛地址: QQ交流 :
《操作系统设计与实现》 Linux系统编程.
Presentation transcript:

第六章 Linux内核

6.1 通用内核职责 总体来说,内核负责抽象与管理一台机器的硬件资源以及这些资源在执行程序之间的共享。内核必须支持进程、文件和其他资源,以使它们能够通过传统UNIX系统调用来进行管理。

6.1.1 资源抽象 资源抽象:是指创造软件(通常情况)来简化必须应用于硬件的操作以使该硬件能恰当地工作。例如,一个设备驱动程序就是一个软件资源抽象。 计算机部件被抽象为进程和资源。 进程管理:操作系统中的所有执行进程抽象的所有方面称为进程管理。 资源管理:是创建资源抽象以及在进程执行时为它们分配和回收系统资源的过程。 UNIX试图将除CPU和可执行内存之外的每一种资源都视为一个文件。

6.1.2 共享资源 进程可以请求、使用并且释放资源。当一个进程请求资源时,它通常需要对该资源的独占使用。对一个资源的独占作用意味着当一个资源被分配给一个进程时,没有其他进程能够访问这个资源。 资源管理有两个关键的方面:对获得资源的竞争和对独占使用的确保。

资源管理程序示意图 请求K个单元 进程P 已分配 资源管理程序 获得K个单元 可用 释放K个单元 资源X 分配K个单元 图6-1 资源管理程序示意图

资源的独占使用 进程对CPU的独占使用--通过确保其他进程无法打断一个进程的执行(除非那些其他进程比当前运行的进程更重要) 内存的独占使用--是通过硬件内存保护机制来保证的。这些机制禁止CPU访问那些没有分配给当前使用CPU的进程的内存。 设备的独占使用是通过以下方式来完成的:禁止CPU对一个设备执行I/O指令,除非是为已分配该设备的进程而执行。

管态与用户态 管态(supervisor mode) :内核执行时CPU处于管态,也称为内核态(kernel mode) 用户态(user mode) :所有其他操作系统部件执行时CPU处于用户态 通过CPU模式位区别 当CPU处于管态时,被认为正在执行信任软件,硬件将会执行在其指令表中的任何指令并且可以访问任何内存地址。 当CPU处于用户态时 ,它被认为在执行非信任软件,硬件将无法执行特权指令(privileged instruction)(例如I/O指令),并且只能访问对当前使用CPU的进程所分配的内存。

6.1.3 操作系统的功能划分 进程与资源管理 存储管理 设备管理 文件管理

6.2 内核的组织结构 单内核结构 一个模块是一个独立的软件单元 设备驱动程序接口 设备驱动程序 Linux内核 模块 接口 模块 6.2 内核的组织结构 单内核结构 一个模块是一个独立的软件单元 设备驱动程序 设备驱动程序接口 Linux内核 模块 接口 模块 图6-2 内核、设备驱动程序和模块

6.2.1 中断 中断是一个产生自外部部件(例如,设备)并由CPU硬件捕获的电子信号,它使CPU开始执行一个程序序列,而该程序序列与中断发生时CPU正在执行的程序无关。

6.2.2 使用内核服务 用户程序将内核看作一个大的抽象数据类型(ADT)(类似于一个对象),它保持状态并在其公共接口——系统调用接口上具有大量函数。 内核软件不具有任何内部执行线程或进程,它只是一组保持状态的函数和数据结构的集合。任何使用内核服务的进程——这种进程是一个活动实体——通过(在逻辑上)使用一个对POSIX的过程调用来产生内核请求。 即一个在内核之外执行的进程当它产生系统调用时开始执行内核代码。

内核作为ADT 用户空间程序调用系统 公共POSIX..1系统调用接口 内 核 私有实现 图6-4 内核作为ADT

管态与用户态之间的切换 陷阱指令(trap instruction)是一条用于将CPU转移到一个预定地址(有时作为一个指令操作数的函数)并将其自身切换为管态的指令。 陷阱指令并不是一条特权指令,因此任何程序都可以执行一条陷阱指令。 转移指令的目的地址是由一组地址预先决定的,它们存放在配置为指向内核代码的管理空间中。

陷阱指令操作 模式 1 S 转移表 陷阱 2 3 ------------------------------------------------------- 信任代码 用户态 管态 图6-5 陷阱指令操作

完成一个系统调用所需执行操作 1. 对于系统调用F,stub(代码存根)过程用于调用F(该stub也称为F)。 4. stub过程确认传递到内核过程的参数值。另外,在原则上它可以验证调用stub过程的进程。 5. stub过程执行一条陷阱指令转换CPU到管态,然后它(通过一张包含内核函数入口点的内核表间接)转移到目的内核函数的入口点。

6.2.3 串行执行 通常内核函数执行时处于临界区。也就是说,一旦进程调用一个系统函数,该函数通常要运行到结束并在CPU分配给不同的进程之前返回。这种类型的内核是单线程(single-threaded)的。 IRQ可以中断系统调用的执行来运行ISR。

6.2.4 守护进程 习惯上,守护进程执行名字以字符“d”结尾的程序。 典型守护进程 syslogd、klogd、crond

认识Linux内核

内核源程序目录结构

内核源程序目录结构 内核源程序代码安装在/usr/src/linux目录下 Documentation:文档 arch:体系结构相关的代码 Drivers:外围设备的软件驱动程序 Fs:Linux支持的所有文件系统 include:这个目录包含了Linux源程序树中大部分的C语言包含(.h)文件。 init:这个目录下面的两个文件中比较重要的一个是main.c,它包含了大部分协调内核初始化的代码 ipc:这个目录包含核心的进程间通讯的代码 Modules :只是一个用来存放建立好的模块的目录

内核源程序目录结构(续) Kernel:Linux中最重要的部分,实现平台独立的基本功能。这部分内容包括进程调度(kernel/sched.c)以及创建和撤销进程的代码(kernel/fork.c和kernel/exit.c) Lib:目录包含两部分的内容。lib/inflate.c中的函数能够在系统启动时展开经过压缩的内核(请参看第4章)。lib目录下剩余的其它文件实现一个标准C库的有用子集 mm:该目录包含了体系结构无关的内存管理代码 net:这个目录包含了Linux应用的网络协议代码 scripts:该目录包含了用来配置内核的脚本

6.3 进程与资源管理

进程抽象 地址空间 虚拟CPU 应用程序 CPU … 地址空间 虚拟CPU 进程管理程序 定时器、中断、… 保护 机制 IPC 调度 程序 内核数据结构 硬件 主存 CPU 图6-9 进程抽象

不同抽象级别的概要 硬件层。硬件从PC指定的内存地址中取出一条指令并执行它,然后再取下一条指令执行。只有硬件进程执行存储指令的概念。 进程管理程序层。进程管理程序创建一组理想化的虚拟机器(virtual machine)。进程管理程序通过使用定时器、中断、各种保护机制、进程间通信(IPC)、同步机制、调度计划以及一组数据结构来使用硬件层创建Linux进程。应用程序与进程管理程序(通过使用系统调用接口)进行交互。 应用程序层。应用程序层使用传统的Linux进程。

1、Linux进程

进程基本状态 基本状态间的转换: 新状态 终止状态 接收 退出 进程调度 就绪状态 执行状态 中断 I/O或事件发生 等待I/O或事件 阻塞状态 新状态 终止状态 接收 进程调度 退出 中断 等待I/O或事件 I/O或事件发生

Linux 进程概况 每个进程分为内核态(特权级0)和用户态(特权级3)两种级别。 2.4.0版本中,每个task_struct 结构占1680字节。 系统中的最大进程数由系统的物理内存大小决定。

Linux 进程状态 Linux进程有以下状态:       Running            进程处于运行(它是系统的当前进程)或者准备运行状态(它在等待系统将CPU分配给它)。       Waiting            进程在等待一个事件或者资源。Linux将等待进程分成两类;可中断与不可中断。可中断等待进程可以被信号中断;不可中断等待进程直接在硬件条件等待,并且任何情况下都不可中断。       Stopped            进程被停止,通常是通过接收一个信号。正在被调试的进程可能处于停止状态。       Zombie            这是由于某些原因被终止的进程,但是在task数据中仍然保留 task_struct结构。它象一个已经死亡的进程。 

Linux进程状态变迁图 停止态 僵死态 正在运行态 不可中断等待态 可中断等待态 就绪态 进程跟踪停止命令 进程终止 被 唤 醒 未申请到所需资源 未申请到所需资源 进 程 调 度 时 间 片 到 不可中断等待态 可中断等待态 所需资源被满足 所需资源被满足 就绪态

进程的结构 Linux下一个进程在内存里有三部分的数据,就是“代码段”、“堆栈段”和“数据段”。 “代码段”,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。 “堆栈段”存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。 数据段存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。 系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。

进程控制块 /usr/src/linux-2.4/include/linux/sched.h struct task_struct { volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ unsigned long flags; /* per process flags, defined below */ int sigpending; mm_segment_t addr_limit; /* thread address space: 0-0xBFFFFFFF for user-thead 0-0xFFFFFFFF for kernel-thread */ struct exec_domain *exec_domain; volatile long need_resched; unsigned long ptrace; int lock_depth; /* Lock depth */ long counter; long nice; unsigned long policy; struct mm_struct *mm; int has_cpu, processor; unsigned long cpus_allowed;

进程ID(PIDs) 每个Unix进程的唯一标志符,范围在0到32,767之间的整数。PID 0和PID 1对于系统有特定的意义;其它的进程标识符都被认为是普通进程。 在Linux中,PID不一定非要唯一——虽然通常都是唯一的,但是两个任务也可以共享一个PID。这是Linux对线程支持的一个副作用,这些线程从概念上讲应该共享一个PID,因为它们是同一个进程的一部分。在Linux中,你可以创建两个任务,并且共享且仅共享它们的PID——从实际使用角度讲它们不会是线程,但是它们可以使用同一个PID。

state 进程的当前状态 TASK_RUNNING TASK_INTERRUPTIBLE TASK_UNINTERRUPTIBLE TASK_ZOMBIE TASK_STOPPED TASK_SWAPPING

flags 进程标志 PF_ALIGNWARN 正在打印"对齐"警告信息 PF_STARTING 正在创建进程 PF_EXITING 进程正在退出 PF_FORKNOEXEC 进程刚创建,但还没执行 PF_SUPERPRIV 使用超级用户特权 PF_DUMPCORE dumped core PF_SIGNALED 进程被信号(Signal)终止 PF_MEMALLOC 正在分配内存 PF_VFORK 对于用vfork创建的进程,退出前正在唤醒父进程 PF_USEDFPU 该进程使用FPU(SMP only)

ptrace PF_DTRACE delayed trace (used on m68k) PF_TRACESYS 正在跟踪 PF_PTRACED 被ptrace系统调用监控

priority 进程优先级,priority的值给出进程每次获取CPU后,可使用的时间(按jiffies计)。优先级可通过系统调用sys_setpriority()改变(kernel/sys.c)

rt_priority rt_priority给出实时进程的优先级,rt_priority+1000给出进程每次获取CPU后,可使用的时间(同样按jiffies计)。实时进程的优先级可通过系统调用sys_sched_setscheduler()改变,不过实际的工作是由setscheduler()完成的,这两个函数均定义在kernel/sched.c中。

counter 在轮转法(round robin)调度时表示进程当前还可运行多久。在进程开始运行时被赋为priority的值,以后每隔一个tick(时钟中断)递减1,减到0时引起新一轮调度。重新调度将从run-queue队列选出counter值最大的就绪进程获得CPU,因此counter起到了进程的动态优先级的作用(priority则是静态优先级)。

policy 该进程的进程调度策略,可以通过系统调用sys_sched_setscheduler()更改(kernel/sched.c)。调度策略有: SCHED_OTHER 0 非实时进程,基于优先权的轮转法(round robin) SCHED_FIFO 1 实时进程,用先进先出算法 SCHED_RR 2 实时进程,用基于优先权的轮转法

进程队列指针 struct task_struct *next_task, *prev_task; 所有进程(以PCB的形式)组成一个双向链表。next_task和prev_task就是链表的前后向指针。链表的头和尾都是init_task(即0号进程)。通过宏for_each_task 可以很方便的搜索所有进程: include/linux/sched.h #define for_each_task(p) \ for (p = &init_task ; (p = p->next_task) != &init_task ; )

进程队列指针 struct task_struct *p_opptr, *p_pptr; struct task_struct *p_cptr, *p_ysptr, *p_osptr; 以上分别是指向原始父进程(original parent)、父进程(parent)、子进程(youngest child)及新老兄弟进程(younger sibling, older sibling)的指针。相关的操作宏参见kernel/linux/sched.h。

父子进程间关系

2、进程管理的相关命令

ps 命令 命令格式:ps [options] [pids] 常用选项: -l 以长列表的形式列出 -a 显示其它用户的进程 -e 显示环境 -r     只显示正在运行的程序

ps 命令

ps 命令 栏目说明: UID 用户的标识号 PID 进程的标识号,是系统赋予每个正在执行的进程的唯一编号 PPID 父进程的标识号 PRI         进程优先级 SIZE       虚拟内存大小 RSS        驻留空间大小 STAT      进程状态。R—正在运行;S—睡眠;D—不可中断睡眠;T—停止或跟踪;Z—僵尸进程;W—没有驻留页 TTY       进程所在的虚拟终端号 TIME      该进程已经运行的时间 COMMAND          该进程的名称

ps 命令

ps 命令

pstree 显示系统进程树结构 常用选项 -a 显示各进程的命令行参数 -p 显示各进程的进程号 -h 对当前进程及其祖先高亮度显示

kill 命令 命令格式: kill [-s 信号 | -p ] [ -a ] 进程号 ... kill -l [信号] 常用选项: -l   输出信号名列表,可以在/usr/include/linux/signal.h文件中找到 -p 指定kill命令只是显示进程的pid,并不真正送出结束信号 -s<信号名>  指出欲发出的信号,信号是以信号名或数字给出的 默认值为TERM信号

& 命令 功能:将进程放到后台运行 方法:在要运行的程序命令的最后加上“&”字符。 例子:(见上页)yes > /dev/null & yes命令:向标准输出发送无穷尽的一串“y”。/dev/null 像一个黑洞,任何被送入这个黑洞的数据都会消失。 [1]——yes的作业号 7840——进程标识号

killall 命令 按名给进程发信号

nice 命令 功能:通过修改调度优先级来运行一个程序。 格式: nice [options][COMMAND[arg…]] 常用选项: -n<adjustment>   加上由<adjustment>指定的优先级而不是默认值10。取值范围是0-19 --version           输出版本信息 nice值小的进程优先級高;nice值大的进程优先級低 

nice 命令

renice 命令 renice命令允许用户修改一个正在运行进程的优先权。 利用renice命令可以在命令执行时调整其优先权。其格式如下:  $ renice -number PID  其中,参数number与nice命令的number意义相同。 注: (1) 用户只能对自己所有的进程使用renice命令。 (2) root用户可以在任何进程上使用renice命令。 (3) 只有root用户才能提高进程的优先权。

skill/snice 报告进程状态

nohup 命令 使进程在用户退出后仍继续执行 命令格式:nohup 命令 结果则会写到用户自己的目录下的nohup.out这个文件里(也可以使用输出重定向,让它输出到一个特定的文件)。

top 命令 功能:显示系统动态的进程控制和进程调度,还可察看内存动态使用的实时信息。 格式:top [options] 字段说明 Uptime   显示系统已运行时间和系统的三个加载均值,加载均值是指那些准备在1分、5分、15分内运行的进程的平均数。 process  显示在最近一次更新是运行的进程总数并作分类,可通过交互命令t来切换进程和状态显示。 CPUstates  显示再用户模式下的CPU利用的百分比、系统方式、优先级任务(优先级任务是指那些优先级为负的)和空闲任务。 Mem  显示内存使用的统计,包含总的可用内存、空闲内存、一用内存、共享内存和用于缓存的内存。可通过交互命令m来切换内存信息显示。

top 命令(续) 字段说明 PID 进程的标识号,是系统赋予每个正在执行的进程的唯一编号 PPID 父进程的标识号 UID 用户的标识号 字段说明         PID                   进程的标识号,是系统赋予每个正在执行的进程的唯一编号 PPID                 父进程的标识号 UID                   用户的标识号 USER                显示任务所有者的用户号 PRI                    进程优先级 NI                      显示任务的nice值 SIZE                  虚拟内存大小 TSIZE               显示任务的代码大小 DSIZE               显示数据加上堆栈的大小,但对ELF进程不显示。 TRS                   显示文本驻留空间的大小。

top 命令(续) 字段说明 SWAP 显示对交换空间的统计,包含总的交换空间,可用交换空间和一用交换空间。 D 显示脏页的大小 LIB       显示用用库页的大小,对ELF进程也无效。 RSS      显示被任务占用的物理内存的总量(以KB计) SHARE 先时任务占用的共享内存的量。 STAT     进程状态。R—进程正在运行;S—睡眠;D—不可中断睡眠;T—停止或跟踪;Z—僵尸进程;W—没有驻留页 TTY       进程所在的虚拟终端号 TIME     该进程已经运行的时间 COMMAND       该进程的名称

top 命令(续) 交互命令: 空格键 立即更新显示。 i 不显示空闲的或僵尸进程信息。 n 或 # 改变显示的进程数量。 q 退出top。 空格键                 立即更新显示。 i                            不显示空闲的或僵尸进程信息。 n 或 #                  改变显示的进程数量。 q                           退出top。 r                           重新调整一个指定进程的优先级。 f 或 F                  在显示时加上或移去字段。 o 或O                  改变显示字段的顺序。 m                         切换内存信息的显示。 t                           切换进程和CPU状态的显示。 

top 命令

uptime 显示系统已运行时间和系统的三个加载均值,加载均值是指那些准备在1分、5分、15分内运行的进程的平均数。

/proc文件系统

/proc文件系统简介 /proc文件系統是一个伪文件系統,它只存在內存当中,而不占用外存空间。

/proc目录列表

/proc下重要文件与子目录 /proc/1 关于进程1的信息目录。每个进程在/proc 下有一个名为其进程号的目录。 /proc/cpuinfo  处理器信息,如类型、制造商、型号和性能。 /proc/devices  当前运行的核心配置的设备驱动的列表。 /proc/dma  显示当前使用的DMA通道。 /proc/filesystems  核心配置的文件系统。 /proc/interrupts  显示使用的中断 /proc/ioports  当前使用的I/O端口。 /proc/kcore  系统物理内存映象。与物理内存大小完全一样,但不实际占用这么多内存 /proc/kmsg  核心输出的消息。也被送到syslog 。

/proc下重要文件与子目录(续) /proc/ksyms 核心符号表。 /proc/loadavg  系统"平均负载";3个没有意义的指示器指出系统当前的工作量。 /proc/meminfo  存储器使用信息,包括物理内存和swap。 /proc/modules  当前加载了哪些核心模块。 /proc/net  网络协议状态信息。 /proc/self  到查看/proc 的程序的进程目录的符号连接。当两个进程查看/proc 时,是不同的连接。这主要便于程序得到它自己的进程目录。 /proc/stat  系统的不同状态 /proc/uptime  系统启动的时间长度。 /proc/version  核心版本。

/proc文件系统

/proc文件系统

/proc进程目录的结构 目录名称  目录內容 Cmdline 命令行参數 Environ 环境变量值 Fd   一个包含所有文件描述符的目录 Mem   进程的內存被利用情況 Stat   进程狀态 Status  Process status in human readable form Cwd   当前工作目录的鏈接 Exe   Link to the executable of this process Maps  內存印象 Statm  进程內存狀态信息 Root    链接此进程的root目录

3、进程创建

创建一个进程 新的进程的创建是通过克隆当前的进程来实现的。一个新的任务是通过系统调用创建的( fork 或 clone ),克隆发生在核心的核心态。在系统调用的最后,产生一个新的进程,等待调度程序选择它运行。从系统的物理内存中为这个克隆进程的堆栈(用户和核心)分配一个或多个物理的页用于新的 task_struct 数据结构。一个进程标识符将会创建,在系统的进程标识符组中是唯一的。但是,也可能克隆的进程保留它的父进程的进程标识符。新的 task_struct 进入了 task 向量表中,旧的(当前的)进程的 task_struct 的内容拷贝到了克隆的 task_struct 。

do_fork系统调用流程

创建一个进程 克隆进程的时候, Linux 允许两个进程共享资源而不是拥有不同的拷贝。包括进程的文件,信号处理和虚拟内存。共享这些资源的时候,它们相应的 count 字段相应增减,这样 Linux 不会释放这些资源直到两个进程都停止使用。例如,如果克隆的进程要共享虚拟内存,它的 task_struct 会包括一个指向原来进程的 mm_struct 的指针, mm_struct 的 count 域增加,表示当前共享它的进程数目。

创建一个进程(续) 创建的子进程与父进程的区别: -进程号(PID) -父进程号(PPID) -所有的文件锁被重置 -收到信号量做的动作不同

一个创建进程的简单例子 #include <stdio.h> #include <sys/types.h> #include <unistd.h> void main(void) { printf("Hello \n"); fork(); printf("bye\n"); }

fork fork: n.叉, 耙, 叉形物, 餐叉; 分岔;岔路 vt, vi 分岔;使成叉形 fork函数原型: #include <unistd.h> pid_t fork(void); /* pid_t是一个unisigned int,是进程号对应的数据类型 */ fork函数创建一个新的进程 对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零。 对父进程而言,它的进程号是由比它更低层的系统调用赋予的,而对于子进程而言,它的进程号即是fork函数对父进程的返回值。

另一个创建子进程的例子 main() { int i, pid; i = 0; pid = fork() if ( pid == -1 ) { printf(“Fork error!\n”); exit(-1); } else if ( pid == 0 ) { /* 子进程程序 */ for ( i = 1; i <1000; i ++ ) printf(“BBB\n”); } else { /* 父进程程序*/ for ( i = 1; i <1000; i ++ ) printf(“AAA\n"); } }

创建子进程的例子

fork过程中父子进程的内存空间内容

一个程序,两个返回值? 子进程 程序 mov … i = 0 push … fork() int … … iret(fork) 系统调用返回 pop move 赋值 jz 判断 父进程 mov … push … 入栈 int … 系统调用 … pop move 赋值 jz 判断 程序 i = 0 fork() … iret(fork) 系统调用返回 pid=…

进程的终止 正常终止 从main函数返回 调用exit函数 调用_exit系统调用函数 进程通过调用系统调用exit自动退出,它在内核中是由sys_exit实现的(23322行)。当C程序从它的main部分返回时,就会潜在调用exit。 异常终止 调用abort函数 进程接收到某信号

进程的终止 当进程退出时,内核释放所有分配给这个进程的资源——内存、文件,等等 内核不能立即回收代表进程的struct task_struct结构,这是因为该进程的祖先必须能够使用wait系统调用查询其子孙进程的退出状态。wait返回它检测出的死亡状态的进程的PID。 处于这种在两种状态之间的进程——它既不是活动的,也没有真正死亡——被称为僵进程(zombies)。sys_exit的任务就是把活动进程转化为僵进程。

等待进程完成 子进程运行结束后(正常或异常),它并没有马上从系统的进程分配表中被删掉,而是进入僵死状态(Zombie),一直等到父进程来回收它的结束状态信息。 如果父进程没有回收走子进程的结束状态就已经退出,子进程将永远处于僵死状态;也有例外,如父进程先于子进程结束,子进程将被init进程继承,并回init进程回收其结束状态信息。

等待进程完成 回收子进程结束状态信息wait, waitpid 函数原型: #include <sys/wait.h> pid_t wait(int *stat_loc); pid_t waitpid(pid_t pid, int *stat_loc, int options);

wait 当进程调用wait,它将进入睡眠状直到有一个子进程结束。wait函数返回子进程的进程id,stat_loc中返回子进程的退出状态。 waitpid的第一个参数pid的意义: pid > 0: 等待进程id为pid的子进程。 pid == 0: 等待与自己同组的任意子进程。 pid == -1: 等待任意一个子进程 pid < -1: 等待进程组号为-pid的任意子进程。 因此,wait(&stat)等价于waitpid(-1, &stat, 0)

wait的例子 int status; int pid; pid = fork(); if (pid == 0) /* child process */ {   printf("\n I'm the child!");   exit(0); } else /* parent process */ {   wait(&status);   printf("\n I'm the parent!")   printf("\n Child returned: %d\n", status) }

执行一个新程序 执行程序系统调用execve 函数原型: #include <unistd.h> int execve(const char *path, const char *argv[], const char *envp[]); Linux还提供其它几个执行程序函数,execl,execlp,execle,execv,execvp都不是系统调用,依赖于execve。

执行一个新程序 path,执行的文件 argv,参数表 envp,环境变量表,一般直接用environ 如: char *argv[] = {“gcc”, “-g”, “-c”, “rbtree.c”, NULL}; execve(“/usr/bin/gcc”, argv, environ);

执行一个新程序 execve启动一个新的程序,新的地址空间完全覆盖当前进程的地址空间,但当前进程把开的文件描述字(除非特别设置),当前工作目录等将被继承。 execve只返回负值表示调用失败,如果成功的话将永不返回。

EXEC的例子 /* using execvp to execute the contents of argv */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> int main(int argc, char *argv[]) { execvp(argv[1], &argv[1]); perror("exec failure"); exit(1); }

思考 exec 与 fork 有何不同? Fork与exec合二为一好不好?

关于建立进程的典型代码段 if ((pid=fork())==-1) { /* 错误处理 */ … } { /* 错误处理 */ … } else if (pid >0) { /*父进程运行,例如wait(pid);*/ …. } else /*子进程运行,例如exec(“ap1”,…);*/ ….}

初始化Linux内核 start_kernel:内核初始化的工作 init:内核运行的第一个用户进程,它要负责触发其它必需的进程以使系统作为一个整体进入可用的状态。 init是系统中所有进程的祖先。 getty进程接受用户登录 getty进程产生login进程,login进程产生用户自己的shell,使用自己的shell,可以产生每一个用户运行的进程。

内核初始化详细过程 参见sys_init.gif

终端上的登录过程

终端登录方式下的UNIX进程层次

进程家族树中的登录过程   init(1)-+-crond(98)               |-emacs(387)               |-gpm(146)               |-inetd(110)               |-kerneld(18)               |-kflushd(2)               |-klogd(87)               |-kswapd(3)               |-login(160)---bash(192)---emacs(225)               |-lpd(121)               |-mingetty(161)               |-mingetty(162)                |-login(403)---bash(404)---pstree(594) |-xinetd---in.telnetd---login---bash              |-syslogd(78)               `-update(166) 

网络登录方式下的UNIX进程层次

UNIX shell工作过程

copy-on-write 写时复制 通过fork创建子进程系统开销很大,需要将每种资源都复制一个副本。这些开销并不是所有的情况下都是必须的,比如某进程fork出一个子进程后,其子进程仅仅是为了调用exec执行另一个执行文件,那么在fork过程中对于虚存空间的复制将是一个多余的过程 Linux中采取了copy-on-write技术,所以这一步骤的所做的工作只是虚存管理部分的复制以及页表的创建,而并没有包括物理也面的拷贝 父子进程以只读方式共享页框,当其中之一要修改页框时,内核才通过缺页异常处理程序分配一个新的页框,并将页框标记为可写。

4、线程

线程 操作系统中线程的概念 线程的分类。分为内核空间的线程和用户空间的线程 进程和线程的区别。 进程和线程之间的最大区别是一个进程的所有线程共享同一个内存空间并共享系统定义的“资源”。这些资源包括打开的文件句柄(文件描述符)、共享内存、进程同步原语和当前目录。因为共享全局内存并且几乎不必分配新内存,所以创建线程比创建进程更简单更快。

Linux线程 Linux内核使用的观点——线程只是偶然的共享相同的全局内存空间的进程。 轻量级进程 glibc 支持用户级线程

clone()系统调用 clone()系统调用接口,用不同的参数指定创建轻量进程还是普通进程。 clone()调用最后会调用do_fork()实现 pid_t clone(int (*fn)(void * arg), void *stack, int flags, void * arg); 返回值同fork

clone()系统调用参数说明 参数:fn是进程所执行的过程,stack是进程所使用的堆栈,flags是CLONE_VM, CLONE_FS(fs记录了进程所在文件系统的根目录和当前目录信息), CLONE_FILES(进程可能的文件 ), CLONE_SIGHAND(自行定义的对信号的处理方式 ),CLONE_PID(Linux内核的clone()没有实现对CLONE_PID参数的支持)的组合。 fork 时 flag = SIGCHLD

clone()系统调用的例子 void * func(int arg) { . . . . . . } int main() {     {     . . . . . .     }   int main()   { int clone_flag, arg; . . . . . . clone_flag = CLONE_VM | CLONE_SIGHAND | CLONE_FS | CLONE_FILES; stack = (char *)malloc(STACK_FRAME); stack += STACK_FRAME; retval = clone((void *)func, stack, clone_flag, arg); . . . . . . }

Linux下的用户空间线程 Linux系统下的多线程遵循POSIX线程接口,称为pthread。 头文件pthread.h,连接时需要使用库libpthread.a。 Linux下pthread的实现是通过系统调用clone()来实现的。 一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。

线程的创建和使用 线程的创建是用下面的几个函数来实现的. #include int pthread_create(pthread_t *thread,pthread_attr_t *attr, void *(*start_routine)(void *),void *arg); void pthread_exit(void *retval); int pthread_join(pthread *thread,void **thread_return);

参数说明 thread是用来表明创建线程的ID,attr指出线程创建时候的属性,用NULL表明使用缺省属性.start_routine函数指针是线程创建成功后开始执行的函数,arg是这个函数的唯一一个参数.表明传递给start_routine的参数. 通过从子例程返回或者调用 pthread_exit() 例程,Linux 线程就退出了。当父线程调用 pthread_join() 例程时,就回收线程使用的资源。pthread_join() 在本质上与进程的 wait() 函数很相似。pthread_join() 不回收线程分配的堆内存。需要由程序或线程来显式地释放全局分配的内存。

线程编程的简单例子 #include <stdio.h> #include <pthread.h> void thread(void){ int i; for(i=0;i<3;i++) printf("This is a pthread.\n"); } int main(void){ pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0){ printf ("Create pthread error!\n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process.\n"); pthread_join(id,NULL); return (0); }

5、进程调度

实时进程与非实时进程 Linux把执行的任务比较紧迫的进程定义为实时进程,这些进程通常会比一般进程先得到机会运行。但这种实时只是软实时,不满足诸如中断等待时间等硬实时要求。两种调度策略:一种执行的任务比较短小,因此采用先进先出的策略;另一种则采用时间片轮转法。 非实时进程是相对实时进程而言的,Linux系统把执行的任务不怎么紧迫的进程统称为非实时进程。Linux通常对这类进程采用优先权算法进行调度。

Linux进程优先级 在Linux中,非实时进程有两种优先级,一种是静态优先级,另一种是动态优先级。实时进程又增加了第三种优先级,实时优先级。

静态优先级 静态优先级——它不随时间而改变,只能由用户进行修改。它指明了在被迫和其它进程竞争CPU之前该进程所应该被允许的时间片的最大值(20)。

动态优先级 动态优先级——只要进程拥有CPU,它就随着时间不断减小;当它小于0时,标记进程重新调度。它指明了在这个时间片中所剩余的时间量(最初为20) 。

实时优先级 实时优先级——值为1000。Linux把实时优先级与counter值相加作为实时进程的优先权值。较高权值的进程总是优先于较低权值的进程,如果一个进程不是实时进程,其优先权就远小于1000,所以实时进程总是优先。

Linux进程调度的策略 进程调度的策略主要考虑以下几个原则: * 高效 使处理器的利用率最高,空闲最小; * 高效 使处理器的利用率最高,空闲最小; * 公平 使每一个申请处理器的进程都得到合理的处理器时间; * 周转时间短 使用户提交任务后得到结果的时间尽可能短; * 吞吐量大 使单位时间内处理的任务数量尽可能多; * 响应时间短 使对每个用户响应的时间尽可能短。

Linux进程的调度算法 时间片轮转调度算法:用于实时进程。 优先权调度算法 FIFO(先进先出) 调度算法 采用FIFO的实时进程必须是运行时间较短的进程,因为这种进程一旦获得CPU就只有等到它运行完或因等待资源主动放弃CPU时其它进程才能获得运行机会。

Linux进程的调度时机 进程状态转换时: 如进程终止,睡眠等; 可运行队列中增加新的进程时; 当前进程的时间片耗尽时; 进程从系统调用返回到用户态时; 内核处理完中断后,进程返回到用户态。

调度优先级 调度优先级:    (1)中断(硬件支持)    (2)当前的核心态进程    (3)其他核心态进程    (4)用户进程(软件管理)

 用户进程的优先级  用户进程的优先级分类    (1)实时进程    (2)交互进程:视为IO bound进程    (3)批处理进程:视为CPU bound进程

具体描述 中断可以抢占核心进程,但必须保证在中断处理结束后返回它。-- 一个核心态进程可以禁用它所运行的处理器上的中断,从而保证不会被中断请求信号中断它的运行。 核心态进程不可抢占中断处理进程。--中断处理期间,仅仅是在中断处理进程处理完成或其他被允许的中断发生时候才发生任务切换。 核心态进程不可抢占其他核心态进程。--除非一个核心态进程自愿睡眠,否则不能切换到别的进程。这保证了核心代码块的对其他进程的整体性,大大的简化了核心保护机制。 特点:核心态进程是非抢占,用户态进程是抢占的。 优点:核心数据的同步和保护机制比较简单。 缺点:不能很好的支持响应速度要求高的实时应用。

普通进程调度算法 对于普通进程,Linux采用动态优先调度 选择进程主要依据counter的大小。 进程创建时,优先级priority被赋一个初值,一般为0~70之间的数字,这个数字同时也是计数器counter的初值。都表示进程的“时间片”。Priority代表分配给该进程的时间片,counter表示该进程剩余的时间片。 在进程运行过程中,counter不断减少,而priority保持不变,以便在counter变为0的时候(该进程用完了所分配的时间片)对counter重新赋值。 当一个普通进程的时间片用完以后,并不马上用priority对counter进行赋值,只有所有处于可运行状态的普通进程的时间片(p->counter==0)都用完了以后,才用priority对counter重新赋值,这个普通进程才有了再次被调度的机会。

Linux进程动态优先级变化过程

时间片 Linux的时间单位也是“时钟滴答”,(Linux为10ms)。 进程的时间片就是指多少个时钟滴答,比如,若priority为20,则分配给该进程的时间片就为20个时钟滴答,也就是20*10ms=200ms。 Linux中某个进程的调度策略(policy)、优先级(priority)等可以作为参数由用户自己决定,具有相当的灵活性。 内核创建新进程时分配给进程的时间片缺省为200ms,用户可以通过系统调用改变它。

实时进程调度策略 两种调度策略,即FIFO(先来先服务调度)和RR(时间片轮转调度)。 实时进程的counter只是用来表示该进程的剩余时间片,并不作为衡量它是否值得运行的标准。每个进程有两个优先级,实时优先级就是用来衡量实时进程是否值得运行的。 Linux用函数goodness()来衡量一个处于可运行状态的进程值得运行的程度。该函数综合了上面提到的各个方面,给每个处于可运行状态的进程赋予一个权值(weight),调度程序以这个权值作为选择进程的唯一依据。

Policy的值 SCHED_OTHER:普通的用户进程,进程的缺省类型,采用动态优先调度策略,选择进程的依据主要是根据进程goodness值的大小。可以被高goodness值的进程抢先。 SCHED_FIFO:这是一种实时进程,遵守POSIX1.b标准的FIFO(先入先出)调度规则。它会一直运行,直到有一个进程因I/O阻塞,或者主动释放CPU,或者是CPU被另一个具有更高rt_priority的实时进程抢先。在Linux实现中,SCHED_FIFO进程仍然拥有时间片-只有当时间片用完时它们才被迫释放CPU。因此,如同POSIX1.b一样,这样的进程就象没有时间片(不是采用分时)一样运行。 SCHED_RR:这也是一种实时进程,遵守POSIX1.b标准的RR(循环round-robin)调度规则。除了时间片有些不同外,这种策略与SCHED_FIFO类似。当SCHED_RR进程的时间片用完后,就被放到SCHED_FIFO和SCHED_RR队列的末尾。

其他 只要系统中有一个实时进程在运行,则任何SCHED_OTHER进程都不能在任何CPU运行。每个实时进程有一个rt_priority,因此,可以按照rt_priority在所有SCHED_RR进程之间分配CPU。其作用与SCHED_OTHER进程的priority作用一样。只有root用户能够用系统调用sched_setscheduler,来改变当前进程的类型(sys_nice,sys_setpriority)。 此外,内核还定义了SCHED_YIELD,这并不是一种调度策略,而是截取调度策略的一个附加位。如同前面说明的一样,如果有其他进程需要CPU,它就提示调度程序释放CPU。特别要注意的就是这甚至会引起实时进程把CPU释放给非实时进程。

直接启动调度 直接启动:发生在当前进程因等待资源而需要进入被阻塞状态时。调度程序执行的步骤如下:     1、把当前进程(全局变量current指向的task_struct变量)放到适当的等待队列里;     2、把当前进程的state设为TASK_INTERRUPTIBEL或者TASK_UNINTERRUPTIBEL;     3、调用schedule(),准备让新的进程掌握CPU;     4、检查当前进程所需的资源是否可用,如果是,则把当前进程从等待队列里删除,否则回到第2步

被动调用调度 通过在当前进程的need_resched设为1,就可以实现另一种方式重新调度各个进程。因为每次调入一个用户态进程之前,这个变量的值都会被检查,因此schedule()又会很快被调用。 被动调用在以下情况下执行: (1)当当前进程用完了它的CPU时间片,update_process_times()重新进行计算。 (2)当一个进程被唤醒,而且它的优先级比当前进程高。wake_up_process()调用reschedule_idle(),设置当前进程的need_resched,使被唤醒的进程尽快掌握CPU。 (3)当sched_setschedler()或sched_yield()系统调用被调用的时候。

主要的进程调度的函数分析 schedule(void):真正执行调度的函数,它选择一个最合适的进程执行,并且真正进行上下文切换,使得选中的进程得以执行。 reschedule_idle(struct task_struct *p):为进程选择一个合适的CPU来执行,如果它选中了某个CPU,则将该CPU上当前运行进程的need_resched标志置为1,然后向它发出一个重新调度的处理机间中断,使得选中的CPU能够在中断处理返回时执行schedule函数,真正调度进程p在CPU上执行。 goodness()函数:用来衡量一个处于可运行状态的进程值得运行的程度。

goodness()函数分析 goodness()函数计算一个处于可运行状态的进程值得运行的程度。一个任务的goodness是以下因素的函数:正在运行的任务、想要运行的任务、当前的CPU。 goodness返回下面两类值中的一个:1000以下或者1000以上。1000或者1000以上的值只能赋给“实时”进程,从0到999的值只能赋给普通进程。实际上,在单处理器情况下,普通进程的goodness值只使用这个范围底部的一部分,从0到41。在SMP情况下,SMP模式会优先照顾等待同一个处理器的进程。 实时进程的goodness值的范围是从1001到1099。 源码在sched.c中。

goodness( ) 计算进程的权值 根据policy区分实时进程和普通进程。 若为实时进程返回权值1000 + p->rt_priority。 若非实时进程,将进程剩余时间片加priority作为权值返回。如果进程是内核线程或用户空间与当前进程相同, 因而不必切换用户空间,则给予权值额外加一的"优惠"。

schedule()函数分析 作用:选择一个合适的进程在CPU上执行,它仅仅根据'goodness'来工作。 流程: ①将prev和next设置为schedule最感兴趣的两个进程:其中一个是在调用schedule时正在运行的进程(prev),另外一个应该是接着就給予CPU的进程(next)。注意:prev和next可能是相同的-schedule可以重新调度已经获得cpu的进程. ②中断处理程序运行"下半部分". ③内核实时系统部分的实现,循环调度程序(SCHED_RR)通过移动"耗尽的"RR进程-已经用完其时间片的进程-到队列末尾,这样具有相同优先级的其他RR进程就可以获得CPU了。同时,这补充了耗尽进程的时间片。 ④由于代码的其他部分已经决定了进程必须被移进或移出TASK_RUNNING状态,所以会经常使用schedule,例如,如果进程正在等待的硬件条件已经发生,所以如果必要,这个switch会改变进程的状态。如果进程已经处于TASK_RUNNING状态,它就无需处理了。如果它是可以中断的(等待信号),并且信号已经到达了进程,就返回TASK_RUNNING状态。在所以其他情况下(例如,进程已经处于TASK_UNINTERRUPTIBLE状态了),应该从运行队列中将进程移走。 ⑤将p初始化为运行队列的第一个任务;p会遍历队列中的所有任务。 ⑥c记录了运行队列中所有进程最好的

6、进程间通信(IPC)

基本概念 为什么需要 IPC 原子操作, 死锁和竞态 同步

管道 最常见的 IPC 机制 通过 pipe 系统调用 #include <unistd.h> int pipe(int filedes[2]); 数据要经过内核传递, 效率较低 只能在关系进程之间进行, 比如父子进程之间 管道是单工的

使用pipe 系统调用的例子 #include <stdio.h> #include <sys/types.h> #include <unistd.h> #define MAXLINE 256 void err_sys (const char* info) { perror (info); exit (1); } int main (void) int n, fd [2]; pid_t pid; char line [MAXLINE]; if (pipe (fd) < 0) err_sys ("pipe error"); if ( (pid = fork ()) < 0) err_sys ("fork error"); else if (pid > 0) { // parent close (fd [0]); write (fd [1], "hello world\n", 12); } else { // child close (fd [1]); n = read (fd[0], line, MAXLINE); write (STDOUT_FILENO, line, n); exit (0);

命名管道 命名管道是一种先进先出(FIFO)的数据结构,它允许两个进程通过管道联接实现信息交换。 在Unix系统中,命名管道是一种特殊类型的文件,因此可以对命名管道进行读写操作;当然,同样 也会有读写和执行等权限的限制。 FIFO 是存在于文件系统的对象, 用 "mknode <name> p", 或 mkfifo (1, 3) 命令或函数建立 通过 FIFO 的通讯可发生在任何两个进程之间, 只要对 FIFO 有适当的访问权限 对 FIFO 的读写操作与普通文件类似 用 FIFO 实现的 C/S 结构

System V 的 IPC 机制 消息传递 共享内存: 效率最高的 IPC 机制, 但没有同步机制 信号量: 实际是一种同步机制, 通常与共享内存一起使用

消息队列 消息队列(message queues)是进程之间互相发送消息的一种异步(asynchronously)方式 四种与队列相关的系统调用: msgget——得到唯一标识一个消息队列的标识号。 msgsnd——向一个消息队列发送一条消息。 msgrcv——从一个消息队列中接受一条消息。 msgctl——在消息队列上执行一组管理操作——检索关于它的限制的信息(比如队列所允许的最大消息数据量)、删除一个队列,等等。

信号量(Semaphores) Linux内核将信号量分为两类: 非实时的(Nonrealtime)——大部分是些传统的信号量,例如SIGSEGV,SIGHUP和SIGKILL。 实时的(realtime)——由POSIX 1003.1b标准规定

共享内存 共享内存(shared memory):一块预留出的内存区域,而且一组进程均可对其进行访问。 共享内存是三种IPC机制里最快的一种,而且也是最简单的一种。 问题:互斥访问

7、进程编程

进程控制相关函数(1) 进程标识符 #include <unistd.h> pid_t getpid (void); pid_t getppid (void); 用户和组标识符 #include <sys/types.h> uid_t getuid (void); uid_t geteuid (void); uid_t getgid (void); uid_t getegid (void);

进程控制相关函数(2) fork: 派生子进程 #include <unistd.h> #include <sys/types.h> pid_t fork (void); vfork: 用于派生后立即 exec 的情况。 * 不复制父进程的全部地址空间. * vfork 保证子进程首先运行, 直到调用 exec 或 exit 之后, 父进程才能得到运行机会. pid_t vfork (void);

进程控制相关函数(3) exit 调用由 atexit 注册的清除函数, 并关闭所有的标准 I/O 流 exit 调用 _exit 处理与 UNIX 相关的内容, 比如文件描述符, 作业控制等 进程的退出状态非常重要. 一般以 0 值表示正常退出 #include <stdlib.h> void exit (int status); #include <unistd.h> void _exit (int status);

进程控制相关函数(4) 进程的退出状态必须用 wait 函数由父进程获取, 否则形成僵尸进程 wait/waitpid * waitpid 可等待指定的子进程退出, 可指定 WNOHANG 选项而不阻塞调用进程的执行. #include <sys/types.h> #include <sys/wait.h> pid_t wait (int *status) pid_t waitpid (pid_t pid, int *status, int options);

进程控制相关函数(5) exec 函数组 #include <unistd.h> extern char **environ; int execl ( const char *path, const char *arg, ...); int execlp ( const char *file, const char *arg, ...); int execle ( const char *path, const char *arg, ..., char * const envp[]); int execv ( const char *path, char *const argv[]); int execvp ( const char *file, char *const argv[]); int execve (const char *filename, char *const argv [], char *const envp[]); * 以可变参数形式传递命令行时, 最后一个参数应该为 NULL 指针. * execve 是实际的系统调用, 而其他的函数以库函数实现, 最终要调用 execve.

进程控制相关函数(6) system 函数 用户和组标识符 getlogin: 获取 login name cuserid: 获取进程有效用户 ID 的关联名称 #include <unistd.h> char * getlogin ( void ); #include <stdio.h> char * cuserid ( char *string );

进程控制相关函数(7) times: 获取当前进程的时间信息, 包括用户态时间, 核心态时间等等 #include <unistd.h> #include <sys/times.h> clock_t times (struct tms *buf); struct tms { clock_t tms_utime; /* user time */ clock_t tms_stime; /* system time */ clock_t tms_cutime; /* user time of children */ clock_t tms_cstime; /* system time of children */ }; * times 返回的时间均以系统启动以来的时钟滴答计算.

源代码中和进程管理相关的文件 include/linux/ * tasks.h: 定义系统进程调度的常量 * sched.h interrupt.h: tqueue.h wait.h: locks.h spinlock.h smp_lock.h timer.h signal.h kernel/ * sched.c softirq.c fork.c itimer.c signal.c ipc/ msg.c shm.c sem.c util.c arch/i386/kernel process.c entry.s traps.c irq.c irq.h include/asm-i386/ processor.h irq.h hardirq.h softirq.h semaphore.h locks.h spinlock.h smplock.h ......

UNIX处理机管理总体实现机制

对该图的说明 1.每个进程的进程管理信息存放在proc结构和user结构中 2. ppda区 3.除了进程0外,每个进程都在两种状态之一运行:核心态和用户态 4.UNIX的CPU调度算法,采用优先级调度算法

LINUXCPU管理实现机制总瞰

Linux进程与任务的关系

Linux任务状态转换图

LINUX的CPU调度过程