操作系统学习笔记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()支持创建全双工且支持远程的通信。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!