1. 进程的地址空间是怎样的?
代码段:
代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。
这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。程序在被载入内存后,会被分为很多小的区(section),有一个rodata区(也就是常量区),常量区的数据内存中的位置为代码段,存放的数据为字符串、const修饰的常量(全局的或是静态的,局部的存放在栈中)
数据段:
数据段(data segment)通常是指用来存放程序中已初始化的全局变量或者静态变量的一块内存区域。
数据段属于静态内存分配。原则上数据段所对应的内存区数据是可以改变的。这里没有提到局部变量,这是因为局部变量一般都存放在栈中。局部变量不管是否有const修饰都存放在栈中
2. 共享锁、互斥锁、自旋锁、RCU锁
2.1 共享锁(S锁、读锁)
如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁,直到已释放所有共享锁。获准共享锁的事务只能读数据,不能修改数据。
2.2 互斥锁(X锁、排他锁、写锁、独占锁)
如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的锁,直到在事务的末尾将资源上的锁释放为止。获准排他锁的事务既能读数据,又能修改数据。
2.3 自旋锁
自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要自旋锁)。
是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。该注意的是,递归程序决不能在持有自旋锁时调用它自己
2.4 互斥锁和自旋锁的加锁原理、区别和应用
加锁原理
- 互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。
- 自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。
区别
互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长。
应用
互斥锁用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑:
- 临界区有IO操作
- 临界区代码复杂或者循环量大
- 临界区竞争非常激烈
- 单核处理器
至于自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下。
2.5 RCU锁
RCU也就是ReadCopyUpdate,包括读标志、写时拷贝、更新时机。主要应用在读多写少的场景之下。
- 读标志:如果一个Reader企图占据一把RCU锁,它是不需要付出任何代价的,只需要设置一个标志,让外界知道有Reader在占据这把RCU锁,多个Reader可以共同持有一把RCU锁。
- 写时拷贝:如果有一个Write企图更新RCU锁所保护的数据,那么它会首先查看该RCU锁的读标志,如果有该标志,说明有最少一个Reader持有了该RCU锁,它需要对原始数据make a copy,写这个副本并将更新过的副本保存在某处,等待时机用该副本更新原始数据。
-
更新时机:这个时机就是用副本更新原始数据的时间点,这个时间点如何确定是RCU锁实现的算法核心,它直接可以确定所有的数据结构。确切来讲,Writer必须waitting for all readers leaving,方可Update原始数据。
3. 堆和栈的理论知识
3.1 申请方式
- 栈:由系统分配(如函数的局部变量)
- 堆:程序员申请,并指明大小
3.2 申请后系统的响应
- 栈:只要栈的剩余空间大于所申请的空间,系统就会为程序提供内存,否则将报异常提示栈溢出
- **堆:**OS中有一个记录空闲内存地址的链表,当系统受到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点,然后把该节点从空闲节点链表中删除,并将该节点的空间分配给程序。对于大多数系统,会在这块空闲空间中的首地址出记录本次分配的大小,这样代码的delete语句才能释放本次内存空间。如果找到的堆大小大于申请大小,剩余的那部分重新放入空闲链表中。
3.3 申请大小的限制
-
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域。
栈顶地址和栈的最大容量是预先规定好的,一般是1MB,申请超过该大小时提示overflow -
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。
堆的大小受限于计算机系统中的虚拟内存。获得的空间比较灵活,也比较大。
3.4 申请效率
- 栈:系统自动分配,速度块,但程序狗无法控制。
- 堆:由new分配,速度慢,容易产生碎片,但使用方便。
3.5 存储内容
- 栈:函数调用时,第一个进栈的是主函数的下一条指令的地址,然后是各个参数。
- 堆:堆的头部用一个字节放存放堆的大小。
3.6 栈和堆区别的简单说明
- 栈空间由OS自动分配/释放,堆空间手动
- 栈空间有限,堆是很大的*存储区
4. 进程和线程的上下文切换
4.1 进程的上下文
- 进程id
- 指向可执行文件的指针
- 栈
- 优先级
- 静态和动态分配的变量的内存
-
处理器寄存器 (当进程执行时寄存器的状态,包括程序计数器和栈指针)
进程上下文的多数信息都与地址空间的描述有关。进程的上下文使用很多系统资源,而且会花费一些时间来从一个进程的上下文切换到另一个进程的上下文。
4.2 线程上下文
- 栈
- 优先级