操作系统学习笔记1

内核设计

微内核

微内核实现了一个功能较少,但是容易扩展的内核架构,客户程序和不同的功能之间提供消息传递功能。除了必须内核功能外,功能组件都作为用户程序来实现。

模块化

例如Solaris,有7种可以在运行时加载的模块。

混合架构例子

  • MacOS X,其Mach内核提供了远程过程调用,进程间通信等功能。BSD内核提供了POSIX库和文件系统等功能。
  • iOS基于MacOSX,在系统的顶层提供了媒体服务用来支持图形化,Cocoa Touch库提供了有触屏硬件支持的Objective-C API
  • Android由Linux内核,增加了一套Dalvik虚拟机和核心库。采用基于Java的Android API用来进行Java开发。运行在Dalvik虚拟机。

调试

D Trace

使用D语言

这个工具可以动态探测运行系统。跟踪系统调用以及指令的运行环境(用户or内核)
DTrace提供内核探头,拥有内核运行的编译器,生成安全指令。通过调用创建的内核探头,执行启用控制块可以捕获一些数据。

操作系统生成

SYSGEN程序用于配置和生成操作系统。系统安装可以有三种情况

  • 极端定制:修改源代码,重新编译系统生成
  • 极端通用:系统描述表已定义好安装的模块,直接激活
  • 折中:选择模块进行链接来生成

编程实例

基础

头文件:

  • linux/init.h
  • linux/kernel.h
  • linux.model.h

重要函数:

  • printk:存储到内核日志缓冲区,用dmesg访问,可以指定优先级
  • module_init() & module_exit():用于注册模块。

编译好的内核模块,使用sudo insmod simple.ko来插入内核
使用sudo ramos simple来移除

数据结构

kmalloc:分配内核内存

进程

进程概念

进程是一个活动实体,包含代码、程序计数器、堆栈等。

进程状态

包括:

  • 新进程:创建进程
  • 运行中:指令执行中
  • 等待:进程等待某个信号
  • 就绪:等待分配处理器
  • 终止:进程已完成

进程控制块

存储了一个进程的相关信息:

  • 进程状态
  • 程序计数器
  • CPU寄存器
  • CPU调度信息
  • 内存管理信息
  • 记账信息
  • IO状态信息

信息采用task_struct来表示,位于<linux/sched.h>
这个结构还存储了父进程、子进程等。
linux系统使用一个current_state结构来指向当前运行的进程。

进程调度

被加载运行的进程,进入任务队列,在内存中等待运行的就是就绪队列,等待IO的进程就放在对应的设备队列

对于整个流程,进程首先被创建,加入到就绪队列,之后被分配到CPU执行时,会有几种可能:

  • 发出IO请求,进入IO队列
  • 创建子进程,等待进程执行结束
  • 中断产生,被放回就绪队列

调度程序

调度程序分为短期调度程序和长期调度程序。

  • 短期调度程序针对的程序IO请求频繁,决策时间较短。
  • 长期调度程序的创建和杀死速度都较慢,因此有更多时间进行调度。

长期调度程序应选择IO于CPU密集型程序并重的进程。

上下文切换

切换进程需要切换状态,典型时间为几毫秒。

进程运行

进程可以产生子进程,因此其组织结构是“树”。init进程是pid为1的进程

重要的init子进程:

  • kthreadd:创建额外内核进程。
  • sshd:创建ssh连接

ps -el 列出进程

fork()函数创建一个子进程,子进程复制父进程的地址空间。它们都执行fork之后的内容。父进程fork()返回子进程pid。子进程返回0。父进程可能会需要wait子进程。

注意:windows的createProcess函数不继承父进程空间,而是需要制定一个特定程序。

父进程可以调用wait,让子进程(僵尸进程)标识符得到释放。并且如果父进程先被终止,如果没有级连终止的要求下,init进程成为子进程的父。

进程间通信

进程和其他进程通信称为协作。进程间协作机制称为IPC,IPC有两种基本模型:共享内存消息传递

目前,在多核系统上,共享内存机制由于高速缓存的不一致性,性能要差于消息传递。

共享内存

共享内存区域驻留在创建共享内存段的进程内。并且负责确保内存不会被同时写入。
共享内存有一个循环数组,用于共享进程发送信息的缓冲。

消息传递

消息传递需要至少提供send()和receive()两个操作。这样要考虑几个问题:

  • 直接or间接通信
  • 同步or异步
  • 自动or显式缓冲

通信的直接or间接

采用直接通信的send()和receive()都需要直接指定接收方的地址,可能是对称或非对称的(非对称即接收方只能接受向其发送的进程信息)

间接通信的方法则通过邮箱或端口来发送信息。通过把邮箱抽象为一个对象,这种方式有如下特点:

  • 共享邮箱才能建立链路
  • 一个链路与多个进程关联
  • 两个进程之间可以有多个链路
    但是一个消息只能被一个进程接收
    邮箱可以为系统或进程拥有,进程拥有的邮箱必须要确定所有者和使用者。所有者只能接受信息,使用者只能发送信息。
    进程被终止后,邮箱将消失。操作系统的邮箱是独立存在的。操作系统提供机制允许进程进行创建、删除、使用邮箱。而且通过系统调用,邮箱的所有权可以传给其他进程。

同步

关于消息传递的同步性,有以下四种可能:

  • 阻塞发送:发送消息后,直到被接收,进程都将阻塞
  • 非阻塞发送:发送后继续操作
  • 阻塞接收:阻塞进程,直到能接收信息
  • 非阻塞接收:接收进程收到有效信息或空信息

缓存

缓存有三种形式:

  • 零容量:要求发送者应当阻塞发送,因为消息队列不能等待。
  • 有限容量:最多n条消息可以等待,超过这个数量时进程将阻塞
  • 无限容量:进程不会阻塞发送

例子

posix

posix通过内存映射文件共享内存,通过系统调用shm_open(name,O_CREAT|O_RDRW,0666)函数来创建共享内存对象。
创建成功后,函数ftruncate(shm_fd,4096)用于配置对象的大小(4096字节)
最后,mmap()函数用于将内存映射文件包含共享内存。返回ptr
使用sprintf()将message写入ptr。
最终消费者使用了共享内存后,调用shm_unlink()移除共享内存

Mach

Mach通过消息传递(采用邮箱)实现
包括两个邮箱:内核邮箱和通知邮箱。调用msg_send()来发送消息,msg_receive()接收消息,msg_rpc()用来进行远程过程调用。
系统调用port_allocate()来创建新邮箱。可以指定最大排队信息,而且消息复制到邮箱中可以保证单个发送者的顺序统一。
发送消息如果遇到邮箱满了,可能会等待(无限或n毫秒),或者立刻返回,或者在操作系统中为一个线程存储一个消息。
邮箱可以形成一个邮箱集合来服务单个任务。port_status()用于返回指定邮箱的消息数量。
Mach本来是为了分布式系统设计,但是为了多核系统,Mach也可以使用虚拟内存,把发送者地址空间映射到接收者地址空间,来提高性能。

Windows

windows支持多个操作环境或子系统,应用程序通过消息传递来通信。Windows使用ALPC工具来进行进程间通信。
类似于TCP连接,Windows内部也使用了连接端口通信端口区分的思想。此外,通信回调机制允许服务器和客户端在等待时也能响应接受请求。
这个机制包含三种技术:

  • 对于小消息,采用消息队列进行存储,复制传递
  • 对于大消息(256字节+):采用区段对象传递,为共享内存。
  • 对于巨大消息,采用API直接读写目标地址空间。

注意:ALPC不属于WinAPI

服务器和客户端通信

套接字

详见计算机网络

RPC

RPC:远程过程调用

RPC和套接字不同,具有明确的数据结构。和本地调用过程相似,RPC隐藏了远程调用的通信细节。
对于每个远程过程,客户端都有一个存根用来调用服务器端口,并传递参数。返回值也可以传递回客户端。
通过时间戳,系统可以避免RPC被重复执行。而且还需要和客户确认RPC调用已经收到且执行。这要求客户机实现RPC调用的发送后接收到ACK信息。
使用交会服务程序,可以让客户请求RPC的端口灵活分配。
RPC可用于实现分布式文件系统

管道

管道是一个半双工的结构
分为普通管道命名管道
UNIX上,管道采用pipe(int fd[])函数来创建,fd[0]为读出端,fd[1]为写入端
父子进程通信可以使用管道来进行,因为子进程继承了夫进程的管道,他们共享一个管道但是有两个fd。但是普通管道需要在同一机器上有父子关系的进程。

命名管道:提供了一个双向的,不必须父子关系的,多进程通信的管道。
而且通信结束后,管道依旧存在。

  • 对于UNIX,管道通过mkfifo()系统调用来进行。通过调用对文件的读写函数来进行常规读写。只有显式删除才会关闭管道。这是半双工且单机通信的,除非用套接字来进行远程通信。

  • 对于Windows,CreateNamedPipe()支持创建全双工且支持远程的通信。