修炼之道 ---其四

序言

我胡汉三回来了,前段时间一直忙于两个课设大作业,经过一段时间的忙碌总算是解决完了,咋们的更新还是照常走起!

但是这次操作系统的课设中,机缘巧合下接触到了 XV6 操作系统,虽然非常的有限,但是作为理解操作系统内核原理是一个不错的开始!我希望将有关操作系统的知识结合源码理解,也许更加的令人印象深刻😋!


一、C++

1. vector的原理,怎么扩容

vector 在底层就是使用 malloc 函数申请的内存空间,使用三个指针来对空间进行管理分别是:

  • start:指向数组的首地址
  • end:指向最后一个元素的地址
  • endofcapacity:指向数组的结尾地址
c 复制代码
这里不了解的需要好好的区别最后两个指针的区别,我用一个图来展示:
[ 1 ][ 2 ][ 3 ][   ][   ]
 ^                     ^
 |                     |
start                 end of capacity
          ^
          |
         end

当数组扩容时 end 指针和 endofcapacity 相遇时则我们的数组容量满了,需要对数组进行扩容。每次扩容时先申请当前容量大小的 1.5 or 2 倍,然后将当前的数组元素拷贝到新的数组当中,然后释放旧的地址。

2. inline 失效场景

inline 的作用是在调用处展开调用函数,而不是进行函数跳转的方式进行调用函数。在这里我们展开讲一讲为什么在内联函数的优化在哪里?我们的函数调用的流程一般是:

  • 跳转到函数地址
  • 参数传递
  • 调用栈帧管理
  • 返回值传递

如果我们调用的函数逻辑非常简单,那么有可能函数跳转的开销还大于函数具体执行的开销,比如:

c 复制代码
int add(int l, int r) {
	return l + r;
}

所以说对于简短的函数就在调用处展开就好,但是也不是什么都是展开函数的好。并且我们声明一个函数为 inline,只是对操作系统建议这个函数可以为内联函数,真正的决定权取决于操作系统。这么说我们的内联函数还可以节约函数栈帧的开销,为什么不把函数都展开呢:

这是因为如果一个稍微复杂的函数如果声明为内联函数,并且四处被调用,就会导致我们这个程序的可执行文件急剧变大!

知识铺垫已经结束了,现在我们来看看内联函数失效的场景:

  • 函数过于复杂:展开函数过于臃肿
  • 递归函数:函数体里面会再次展开,无法编译时确定展开次数
  • 虚函数的多态调用:多态是动态运行的,无法再编译器确定具体的调用函数

3. C++ 中 struct 和 class 区别

struct 是在 C 语言中我们构造一个简单的数据结构所采用的方式,而 class 是 C++ 提供给我们封装一个对象的方法,后者的功能更加的丰富。两者的区别是:

  • struct 中默认访问是 public,而 class 是 private
  • struct 中默认继承是 public,而 class 是 private
  • 前者主要用于定义一个简单的数据结构,后者用于封装一个类

4. 友元 friend 函数介绍

友元函数的功能主要是让类外的函数可以访问私有成员变量,即使他不是类成员函数。使用方式为:

c 复制代码
class Box {
private:
    double width;

public:
    Box(double w) : width(w) {}

    // 声明友元函数
    friend void printWidth(Box b);  // 友元函数声明
};

// 定义友元函数
void printWidth(Box b) {
    // 友元函数可以直接访问 Box 的 private 成员
    cout << "Width: " << b.width << endl;
}

二、操作系统

1. 信号量和自旋锁的区别

首先两者都是用于线程同步的方法,但是背后的处理逻辑稍有不同:

  • 自旋锁采取轮询的方式来查看资源是否就绪;信号量会将线程阻塞放入等待队列,直到资源就绪
  • 信号量可以让多个进程共享资源,而自旋锁只能实现互斥的功能

2. fork() 读时共享写时拷贝

fork() 函数是一个系统调用,创建一个子进程,创建的子进程会复制父进程的进程地址空间,但是这里是类似浅拷贝的方式将父进程的物理页映射到子进程的页表上。

子进程的物理页会被去除写权限,当子进程对该物理页进程写操作时会触发缺页中断,此时操作系统才会额外的为子进程申请一个单独的物理页,并将数据拷贝到该物理页上。

这样操作的话,就避免了将只读的数据拷贝到子进程,提高了内存的使用效率,同时维护了父子进程的之间的独立性。

3. 什么是死锁?如何避免死锁?

死锁是指占有资源(上锁)的两个或多个线程持有对方所需要的资源导致循环等待,程序无法正常运行的情况。

如何避免死锁呢,方式有:

  • 在编程中尽量避免多重上锁
  • 上锁指定时间时间内没有释放锁,自动执行释放锁的操作
  • 持有锁的情况下,使用 try_lock 上锁,避免直接上锁导致死锁

4. 什么是虚拟内存?为什么需要虚拟内存?

进程使用的地址空间就是虚拟内存,虚拟内存在逻辑上连续的,并且每一个进程认为自己是独享整个内存空间的。但是实际上进程肯定是存在于物理内存上的,使用页表的机制来记录虚拟内存的对应关系,当 CPU 访问对于的虚拟内存时会通过页表映射到对应的物理内存上面。

虚拟内存的作用有:

  • 进程隔离:每个进程都在自己的虚拟内存,不会相互干扰
  • 内存拓展:每个进程可以使用磁盘作为拓展,提供⽐实际可⽤内存更多的内存
  • 提高内存的使用率:将不常用的物理页交互到磁盘上,让内存腾出空间给真正需要使用的进程

总结

许久不更了,后面空闲的日子每天坚持一更,加油加油!

相关推荐
xuanzdhc20 分钟前
Linux 基础IO
linux·运维·服务器
愚润求学26 分钟前
【Linux】网络基础
linux·运维·网络
bantinghy1 小时前
Linux进程单例模式运行
linux·服务器·单例模式
小和尚同志2 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
帽儿山的枪手2 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
shadon1789 天前
回答 如何通过inode client的SSLVPN登录之后,访问需要通过域名才能打开的服务
linux
小米里的大麦9 天前
014 Linux 2.6内核进程调度队列(了解)
linux·运维·驱动开发
LyaJpunov9 天前
深入理解 C++ volatile 与 atomic:五大用法解析 + 六大高频考点
c++·面试·volatile·atomic
小灰灰搞电子9 天前
Qt PyQt与PySide技术-C++库的Python绑定
c++·qt·pyqt
算法练习生9 天前
Linux文件元信息完全指南:权限、链接与时间属性
linux·运维·服务器