简介
本文内容为学习Linux/UNIX系统编程手册时的学习笔记与总结
什么是操作系统、内核
内核的职责
- 进程调度(CPU切换,资源调度等)
- 内存管理
- 提供文件系统(文件的创建删除等)
- 创建和终止进程
- 对设备的访问(鼠标、键盘等输入输出设备)
- 联网
- 提供系统调用接口(API)
内核态与用户态
进程
- 程序: 包含了一系列信息的文件。
- 进程:一个可执行程序的实体。由内核定义的一个是抽象的实体,内核同时为该抽象实体分配用以执行程序的各项系统资源(内存,CPU等)
- 进程的组成:从内核角度看,进程由
用户内存空间
和一系列内核数据结构
组成。其中用户内存空间包含了程序代码及代码锁使用的变量。而内核数据结构则是用于维护进程状态信息(进程相关标识号,虚拟内存表,打开文件描述符,信号传递及处理相关信息,进程资源使用及限制,当前工作目录等信息)
Init进程
所有进程之父~
守护进程
常驻的进程,级别仅次于Init进程
环境列表(环境变量列表)
进程中的公共变量,入HOME,PATH等
系统调用
内存
典型的内存进程内存结构
虚拟内存
虚拟内存是相对于物理内存RAM的一个概念,用来提高物理内存的使用效率的一个设计。
虚拟内存将所有的内存分页
(一般为4096字节一页),在进程中维护一个页表。
每张页表对应一个物理页或者是磁盘的一个空间。
设计起因
虚拟内存的优点
维基百科解释
虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,对真正的物理内存(例如RAM)的使用也更有效率。
注意:虚拟内存不只是“用磁盘空间来扩展物理内存”的意思——这只是扩充内存级别以使其包含硬盘驱动器而已。把内存扩展到磁盘只是使用虚拟内存技术的一个结果,它的作用也可以通过覆盖或者把处于不活动状态的程序以及它们的数据全部交换到磁盘上等方式来实现。对虚拟内存的定义是基于对地址空间的重定义的,即把地址空间定义为“连续的虚拟内存地址”,以借此“欺骗”程序,使它们以为自己正在使用一大块的“连续”地址。
进程、内存、环境总结
堆内存管理(申请、释放、扩展)
大家熟悉的应该是C中的malloc与free方法。
然而在这两个方法背后,隐藏着另外两个方法:brk 与 sbrk。这两个方法是用来扩展堆内存区域的范围的(以虚拟内存页大小为单位来扩展)
首先,需要了解的是,每次malloc申请的内存,在通过free方法释放后,并没有放回堆内存的顶部,也就是位置没动。而是插入到一个空闲内存双向链表中,这样的特性导致内存模型如下:
那么问题来了,这里的链表pre指针和next指针存放在哪。还有free的时候是如何知道内存空间的大小的。
其实,在malloc的时候,会多申请一部分空间来存储申请得到的内存大小:
这样就能保证调用free的时候,能够知道内存块大小。
另外一个问题,就是释放内存后,pre以及next指针的数据存放问题,其实是通过在这块空闲的内存空间前面,取一部分空间来存放pre和next指针的数据:
综合上面的内容,就能够理解为何操作内存的时候,不能操作申请的内存区域之外的内存区域,这将导致用来存放长度,以及pre和next的数据被修改导致不可预知的问题。
其他内存申请方法
尽量避免使用realloc方法调整内存大小,原因如下:
用户和组
实际用户ID和实际组ID
登录的时候的用户ID即为实际用户ID,当创建新进程的时候,子进程会继承父进程的实际用户ID和实际组ID
有效用户ID和有效组ID
假设登录的用户ID为A
,组ID为B
通过chmod u+s xxx
可以为xxx
可执行文件设置有效用户ID为当前进程的用户ID: A
。
通过chmod g+s xxx
可以为xxx
可执行文件设置有效组ID为当前进程的组ID:B
。
当执行xxx
可执行文件时,启动的进程有效用户ID和有效组ID即为A
和B
文件IO,缓冲区,高速缓存
个人理解的是,在用户内存空间和内核内存空间都有开辟一部分内存区域用做文件IO的缓冲区使用。以减少对磁盘的直接读写,加快IO速度。
弊端: 写入到内核高速缓冲区的数据,如果碰上断电的情况,将会导致数据的丢失。
缓冲区高速缓存与页高速缓存
更多内容参考:Linux 内核之页高速缓存与页回写
缓存与缓冲
缓冲区与特定的块设备相关联,并覆盖缓存 文件系统元数据以及跟踪正在进行的页面。缓存 仅包含停放的文件数据。也就是说,缓冲区记住了什么 在目录中,文件权限是什么,并跟踪什么 正在为特定块设备写入或读取存储器。 缓存仅包含文件本身的内容。
更多内容参考:页高速缓存里面的缓冲区高速缓存
I/O缓冲小结
设备专用文件(设备文件)
Unix上,所有设备都有驱动程序,用来处理设备的I/O请求。驱动程序有一套公用的系统调用,包含open()、close()、read()、write()、mmap()、以及ioctl()。其中ioctl使用起来会想想对复杂一些。所有设备的接口都保持统一,隐藏了每个设备在操作方面的差异,从而满足的了I/O的操作的通用性。
某些设备是实际存在的,比如鼠标,键盘灯。而另一些设备则是虚拟的,亦即并不存在响应的硬件,但内核会(通过设备驱动程序)提供一种抽象设备,其所携带的API与真实设备无异。
设备可分为以下两种:
- 字符设备基于每个字符来处理数据。终端和键盘都属于字符设备。
- 块设备则每次处理一块数据。块的大小取决于设备类型,但通常为512字节的倍数。磁盘和磁带都属于块设备。
设备ID
进程间通信
- 通信(数据交换)
- 信号
- 同步
管道
- 管道是一个字节流
- 管道是单向的
- 写入数据不超过
PIPE_BUFF
字节的数据可以保证为原子操作(可以被信号中断) - 管道的容量有限(管道是内核内存中维护的缓冲器)
- 管道通过
pipe();
方法创建 - 管道通常用于两个兄弟进程之间的通信(父进程创建管道,然后创建两个子进程,
相关进程
) - 负责写入的进程,需要关闭读取文件描述符,负责读取的进程,需要关闭写入的文件描述符(不关闭读取描述符,无法收到错误,不关闭写入描述符,无法收到关闭通知,会一致阻塞)
FIFO
与管道基本一样,FIFO被称为命名管道
与管道的不同点:
- 通过
mkfifo()
创建 - 在文件系统中存在一个名称
- 可以被拥有合适的权限
- 任意进程打开(权限允许的话),也即是不仅仅用于
相关进程
- 为写入而打开一个FIFO时会被阻塞直到另一个进程以读取打开了该FIFO(反之亦然)
消息队列
- 消息队列用于进程间传递数据
- 消息队列也有标识符,但是该标识符不能通过
write
,read
方法来操作,它不是一个IO模型,也不是一个文件标识符。 - 消息队列根据业务提供的
key
生成对应的唯一标示 - 同样的
key
会生成(只生成一次)相同的标识符。 key
的来源,可以通过公用一个头文件,并把key
放在头文件重定义(容易出现重复)- 通过
ftok()
方法能生成唯一的key(重复概率极小,通过文件的i-Node
属性已经一个整型的低位计算而得) - System V 的消息队列有很多限制,比如写入的消息长度限制,写入的消息条数限制,整个系统的消息队列个数限制等。
信号量
- 信号量用于进城之间同步(资源竞争,共享内存的读写同步)
- 信号量使用与消息队列一样的标识符,不支持IO模型方法操作
- System V的信号量设计的比较复杂(Linux 2.6内核之后可以使用POSIX信号量)
- 信号量一次需要创建一组标识符
- 信号量的创建于初始化不在一个系统调用,容易造成创建和初始化之间多进程竞争的问题
共享内存
- 共享内存是一种最快的进程间通讯方式,但是缺点是无法保持同步,需要使用一种进程间同步机制来保证进程间对共享内存区域的操作(比如信号量)
- 将共享内存页绑定到虚拟内存中时,尽量选择让系统配置内存起始地址
- 共享内存页绑定在虚拟内存列表
堆与栈中间部分
- 共享内存因为可以被多个进程绑定,所以在存储指针的时候,不要直接存储指针地址(虚拟内存地址),而是通过偏移量来存储指针地址,原因是当该段内存页绑定到其他进程时,存储的指针地址会失效
共享内存、内存映射、以及共享库的位置
通过cat /proc/进程ID/maps
查看指定进程映射的共享内存以及共享库的位置信息
maps文件内容解释
内存映射
简介
- 内存映射分为
基于文件内存映射
和匿名内存映射
,文件内存映射将文件内容映射进虚拟内存空间中,匿名映射通过MAP_ANONYMOUS
标记或者dev/zero
来创建,映射中的数据都会被初始化为0 - 映射可以是私有的(MAP_PRIVATE),也可以是共享的(MAP_SHARED)。只有共享内存映射才会将映射的内存区域变化反映到文件中,也只有共享内存映射才能与其他进程共享内存区域的变化。
- 共享内存映射将变更刷新到文件的时机不确定,可以通过
msymc()
系统调用请求刷新(不一定实时)
用途
- 分配进程私有的内存(私有匿名映射)
- 对进程中
文本段
以及初始化数据段
中的内容进行初始化(私有文件映射) - 通过
fork()
共享相关进程的内存(共享匿名映射) - 执行内存映射IO,将不同进程之间的IO联系起来,也可作为普通文件IO的替代方案(特定情况下) (共享文件映射)
错误使用
- 违反访问权限,或者访问当前未被映射的地址,将会产生SIGSEGV信号。
- 对于文件映射来讲,如果访问的地址在文件中没有相应的区域与之对应,将会产生SIGBUS信号。
私有文件映射
内存映射IO
什么是内存映射IO,简单的讲就是通过内存映射来实现文件的读写,替换read/write,提高效率。
内存映射IO的优势
不适合使用内存映射IO的情况
查看系统内存分页大小
通过sysconf(_SC_PAGESIZE)
查看
Socket
流Socket
流Socket的运行与电话系统类似
流Socket IO
Socket总结
- Socket采用IP地址来标识通讯地址
- Socket分为
流Socket
和数据报Socket
流Socket
是可靠的,双向的字节流通道数据报Socket
是不可靠的、无连接的、面向消息的通信- 一个典型的
流Socket
,服务器会创建socket,然后通过bind绑定socket,然后通过listen代表接口该socket上的连接。接着通过accept阻塞等待连接的到来。客户端创建socket,通过connect连接到服务器socket上。之后两端便可以通过write和read进行通讯。 - 一个典型的
数据报Socket
,服务器会创建socket,然后通过bind绑定socket,因为数据报Socket
是无连接的,所以服务器可以同时接收多个客户端的数据,通过read或者recvfrom系统调用接收数据报,其中recvfrom能够返回发送数据的socket地址。客户端创建socket之后,通过sendto向指定地址的服务器发送数据报。客户端也可以通过connect来设置一个对等地址,设置完成后就无需为发送出去的数据报设置目标地址了。write调用可以用来发送一个数据报。
TCP/IP 网络基础
互联网协议和层
通过TCP/IP协议进行的分层通信
总结
数据链路层
网络层中最底层,由设备驱动和底层物理媒介(如电话线、光纤)的硬件接口(网卡)构成。其关注的是在一个网络的物理连接上传输数据,数据单元为:帧
,每一帧有最大传输单元值(MTU)
网络层(IP)
数据链路层的上一层,其关注的是如何将包(数据)从源主机发送到目标主机,主要包含以下任务
- 将数据分解成小于MTU的片段(如果有必要的话)
- 在因特网上路由数据
- 为传输层提供服务
网络层IP具备如下特性IP是无连接的
、不可靠的
IP地址:IPv4和IPv6,主要由网络ID和主机ID构成。
子网掩码:用来分开网络ID和主机ID
传输层
传输层使用最广泛的协议为用户数据报协议(UDP)
、传输控制协议(TCP)
- UDP是不可靠的,无连接的,在网络层的基础上只做了一个校验和。
- TCP是可靠的,面向连接的,双向字节通信信道
TCP协议的特性
- 需要建立连接
- 数据会被打包成段,每一个段使用单个IP数据段来传输
- 确认、重传以及超时
- 排序,被拆分的段会被排序,接收方通过序号重新拼接成一个TCP数据包
- 流量控制:接收端需要创建一个数据缓冲区来接受数据,并在每个确信中,带上数据缓冲区的可用空间
- 拥塞控制:慢启动和拥塞避免算法(丢失了就认为是拥塞)