Colderleo's blog Colderleo's blog
首页
Linux
C++
Python
前端
工具软件
mysql
索引
关于
GitHub (opens new window)

Colder Leo

热爱代码的打工人
首页
Linux
C++
Python
前端
工具软件
mysql
索引
关于
GitHub (opens new window)
  • 常见程序性能开销cost、latency延迟耗时的经验值
  • 面试常见问题
    • linux
      • 页表、内存的访问:
      • 堆和栈内存是如何分配的,哪个访问更快?
      • volatile
      • 无锁队列: spsc、mpmc等
      • memory order、内存同步:
      • 动态链接和静态链接:
      • 常见程序行为耗时:
      • 线程通信:
    • 编译、链接和hook
      • 在源文件和静态库中同时定义同名全局变量会怎么样?
      • 如何hook一个so中的函数?
      • preload原理
      • 延迟重定位的原理
    • 进程调度
      • 介绍下RCU的原理机制
      • 如何修改时间片的长度?内核如何得知一个进程的时间片用完?
    • c++
      • c++ 没有默认构造函数的类,不能调用无参构造。
      • 左值和右值:
      • std::function、 lambda表达式 底层原理
      • nullptr和NULL的区别
      • stl中map/set、unorderedmap/unorderedset、vector、deque、priority_queue等底层实现
      • 智能指针底层原理:
    • 网络通信
      • 对端地址可以ping通,但是connect连接失败提示no route to host,可能是什么原因
      • epoll中et和lt的区别与实现原理
      • tcp延迟确认
      • systemtap/bpftrace原理和使用
      • gdb+汇编
      • 大小端和网络字节序
      • 如何在头文件中定义全局变量,被多个cpp包含时而不出错
    • 算法题
    • 基础题目
    • 智力题
    • 模板
    • 问答题
  • 静态链接-动态链接-elf详解-elfloader
  • 动态库和静态库的依赖问题
  • glibc和ld的编译调试-为某程序单独设置ld
  • dl_iterate_phdr遍历linkmap头、获取so加载地址
  • shell、bash语法和脚本模板
  • so文件查找路径
  • 逻辑地址-线性地址or虚拟地址-物理地址
  • 通过ELF读取当前进程的符号表并获取符号的版本信息
  • 虚拟内存,cache,TLB,页表
  • 用户内存空间分布和mmap
  • numa网卡绑定
  • 隔核绑核、服务器优化
  • popen底层原理和仿照实现-execl
  • tmux用法
  • ASLR机制
  • 程序后台运行、恢复前台nohup
  • 大页内存huge_page
  • 用perf查看page-fault
  • Bash设置显示全部路径
  • 查看socket fd状态,设置nonblock
  • cout输出到屏幕的过程
  • 多进程写同一文件-write原子性-log日志
  • vim用法
  • epoll用法
  • signal信号、软中断、硬中断、alarm
  • 内核模块
  • 读写锁之pthread_rwlock和内核rwlock自旋读写锁
  • systemtap
  • xargs、awk用法
  • openssl libssl.so.10.so缺失问题
  • netstat用法
  • fork函数
  • tcp延迟确认ack
  • 90.centos7上一次std-string编译错误定位
  • docker用法
  • find用法
  • dmesg
  • gcc编译用法
  • avx-sse切换惩罚
  • Centos7防火墙
  • chmod用法
  • kernel-devel安装版本
  • Linux-Centos7系统安装、网络设置、常见报错
  • linux下g++编译c++文件
  • MegaCli 安装及使用
  • mysql
  • mysql忘记密码修改方法
  • set用法
  • crontab
  • ssh传文件scp
  • ssh连接
  • tcpdump、tshark、tcpreplay用法
  • ubantu root登录以及创建新用户
  • ubuntu安装g++和gdb
  • uClibc编译失败解决方法
  • win10安装WSL open-ssh
  • yum升级git
  • 比较so文件版本-md5sum
  • 查看磁盘信息
  • 合并两个硬盘,挂载到一个文件夹下
  • 软件安装目录usr-local-src
  • 下载centos历史版本
  • sh脚本转可执行文件、加密
  • Linux
gaoliu
2022-06-03
目录

面试常见问题

# linux

  • 用户内存空间分布、mmap: https://colderleo.github.io/pages/44a400/ (opens new window)

# 页表、内存的访问:

  • https://colderleo.github.io/pages/e9c8c3/ (opens new window)
    1. 虚拟地址转换为物理地址: 先访问tlb,如果tlb miss了,则要查页表。
    1. 根据物理地址访问cache或内存,内存如果不在实际内存上,则产生缺页异常,从硬盘上加载

# 堆和栈内存是如何分配的,哪个访问更快?

* 栈内存通过栈指针下移分配,分配速度非常快,一个指令即可。
* 堆内存通过malloc分配,分配速度慢, 
* 栈内存访问较快,定义在栈上的变量直接访问,不需要指针跳转;栈不容易产生cache miss
* 堆内存访问较慢,一是访问时通过指针访问,要多一次跳转,二是堆内存更容易产生cache miss
* malloc申请的内存较小时使用brk(),较大时使用mmap()

# volatile

# 无锁队列: spsc、mpmc等

# memory order、内存同步: https://colderleo.github.io/pages/76ca3b/ (opens new window)

* volatile是编译阶段生效的,不是运行时的属性。相当于是std::memory_order_relaxed

# 动态链接和静态链接: https://colderleo.github.io/pages/02cb3a/ (opens new window)

* c/c++编译单元是c或cpp文件
* preload生效的原理,示例: <https://colderleo.github.io/pages/d5a573/>

# 常见程序行为耗时: https://colderleo.github.io/pages/a4e7dc/ (opens new window)

# 线程通信:

* 条件变量  <https://colderleo.github.io/pages/813113>
* 信号量   和条件变量类似,只不过条件变量更丰富一些
* mutex    mutex如果不发生竞争,则不会陷入内核,耗时较短,该特性称为futex
* spinlock
* 无锁队列:  数据经过无锁队列大概耗时50-100ns
* eventfd、pipe
* 信号: <https://colderleo.github.io/pages/a59851>

# 编译、链接和hook

# 在源文件和静态库中同时定义同名全局变量会怎么样?

会编译报错。 - 如何编译不报错? 将其全部定义为弱符号 weak,或者除了其中一个,其他全部定义为弱符号 - 在主程序和so中同时定义同名全局变量会怎么样? 主程序中的全局变量会覆盖so的全局变量,主程序和so中对全局变量的修改都指向主程序中的变量,仿佛so中的全局变量定义不存在。 - 在两个so中同时定义同名全局变量会怎么样? 会使用链接指令中靠前的那个so中的变量。

# 如何hook一个so中的函数?

方法一: preload、 方法二: 在主程序代码中hook: 修改主程序中该函数的plt条目。 - 如何获取plt地址? 可通过jump指令或者主程序的elf加载信息获取函数的plt地址。 - 该方法与preload相比有何风险? 主程序对该函数的调用会被hook,但是so中的调用不会被hook。如果想hook so中对该函数的调用,需要修改so的plt条目。 方法三: 热补丁,在程序运行时,通过ptrace依附到目标进程,然后采用方法二进行修改。

# preload原理

动态连接器在符号重定位(symbol resolution/relocation)时,将preload指定的so放在查找队列的前面,从而覆盖主程序和其原本依赖的so的符号。

# 延迟重定位的原理

只有导出的符号可以被重定位 只有函数可以被延迟重定位,变量不可以。 可重定位的函数在执行时,跳转到plt条目中的地址,也就是真实的函数地址。延迟重定位就是plt条目中的初始地址指向符号重定位函数,而不是真实的函数地址,在函数第一次执行的时候,先执行了符号重定位函数,该函数将plt修改为真实函数地址并执行。在以后执行该函数时就是直接跳转到真实函数地址了。

# 进程调度

# 介绍下RCU的原理机制

参考: https://blog.csdn.net/zhoutaopower/article/details/86646688 (opens new window)

使用示例,from exanic源码

//reader
    rcu_read_lock();
    tcp = exasock_tcp_lookup(iph->saddr, iph->daddr, th->source, th->dest);
    rcu_read_unlock();

// writer
    spin_lock(&tcp_wait_death_lock);
    //临界区,更新rcu链表,这个链表是用户自己定义的,更新也可以按照自己的方式更新。
    list_del_rcu(&tcp->death_link);
    //其他rcu相关函数: rcu_assign_pointer(ptr, new_ptr); list_replace_rcu(old, new); 等等
    spin_unlock(&tcp_wait_death_lock);

    // 更新链表之后调用synchronize_rcu()来释放旧节点。
    synchronize_rcu();
1
2
3
4
5
6
7
8
9
10
11
12
13
14

rcu_read_lock()使当前cpu变为不可抢占(不能响应中断)的状态。

synchronize_rcu(),在writer更新节点后调用,该函数会向其他所有cpu发起rcu同步请求,该请求是一个中断信号,其他cpu如果都响应了,则表明它们都没有在访问旧节点,这时就可以释放旧节点了。

为什么所有cpu都响应就表明没有reader在读旧节点: 因为如果有reader在读旧节点,rcu_read_lock()会禁用cpu中断,它就无法响应。而更新节点后如果又有reader读取,它会直接读新节点,不会读旧节点。

# 如何修改时间片的长度?内核如何得知一个进程的时间片用完?

可通过修改内核CONFIG_HZ参数并重新编译内核来设置时间片长度。一般是在100 ~ 1000之间,默认250hz,即4ms https://blog.csdn.net/weixin_52622200/article/details/122992817 (opens new window) 由cpu时钟中断触发,在中断处理函数中判断其时间片是否用完; -- 是否可以禁用该中断? 可以针对不同cpu设置nohz参数以屏蔽该中断。 查看时间片长度(通常是100ms): cat /proc/sys/kernel/sched_rr_timeslice_ms 进程何时进行调度(抢占)? 参考: https://blog.csdn.net/gatieme/article/details/51872618 (opens new window)

源码级分析: <https://blog.csdn.net/sfrysh/article/details/5809869>
  • 首先进程会检查是否需要调度,并设置调度标记 检查发生在以下情况: scheduler_tick 时钟中断时、wake_up_process 唤醒进程时、do_fork 创建新进程时、set_user_nice 修改进程nice值时、smp_send_reschedule 负载均衡时

  • 进程调度(抢占)就是执行schedule()函数,主要在以下时机:
    进程被阻塞、 进程从中断处理程序或系统调用返回用户空间时、 时间片用完时、 进程主动调用系统调度相关函数时、进程在内核态调用解锁等函数使其变为可抢占时。

  • 进程调度时机详细情况如下,分为用户抢占和内核抢占:

    • 用户抢占发生几下情况:

      • 从系统调用返回用户空间;
      • 从中断(异常)处理程序返回用户空间 从这里我们可以看到, 用户抢占是发生在用户空间的抢占现象. 更详细的触发条件如下所示, 其实不外乎就是前面所说的两种情况: 从系统调用或者中断返回用户空间
      • 时钟中断处理例程检查当前任务的时间片,当任务的时间片消耗完时,scheduler_tick()函数就会设置need_resched标志;
      • 信号量、等到队列、completion等机制唤醒时都是基于waitqueue的,而waitqueue的唤醒函数为default_wake_function,其调用try_to_wake_up将被唤醒的任务更改为就绪状态并设置need_resched标志。
      • 设置用户进程的nice值时,可能会使高优先级的任务进入就绪状态;
      • 改变任务的优先级时,可能会使高优先级的任务进入就绪状态;
      • 新建一个任务时,可能会使高优先级的任务进入就绪状态;
      • 对CPU(SMP)进行负载均衡时,当前任务可能需要放到另外一个CPU上运行
    • 内核抢占发生的时机,一般发生在:

      • 当从中断处理程序正在执行,且返回内核空间之前。当一个中断处理例程退出,在返回到内核态时(kernel-space)。这是隐式的调用schedule()函数,当前任务没有主动放弃CPU使用权,而是被剥夺了CPU使用权。
      • 当内核代码再一次具有可抢占性的时候,如解锁(spin_unlock_bh)及使能软中断(local_bh_enable)等, 此时当kernel code从不可抢占状态变为可抢占状态时(preemptible again)。也就是preempt_count从正整数变为0时。这也是隐式的调用schedule()函数
      • 如果内核中的任务显式的调用schedule(), 任务主动放弃CPU使用权
      • 如果内核中的任务阻塞(这同样也会导致调用schedule()), 导致需要调用schedule()函数。任务主动放弃CPU使用权

# c++

# c++ 没有默认构造函数的类,不能调用无参构造。

<https://colderleo.github.io/pages/a1b204/>
* 虚析构函数、explicit、inline的特性、

# 左值和右值: https://colderleo.github.io/pages/6a25c0/ (opens new window)

# std::function、 lambda表达式 底层原理

# nullptr和NULL的区别

# stl中map/set、unordered_map/unordered_set、vector、deque、priority_queue等底层实现

  • priority_queue: 堆, TopK问题
  • unordered_set: 哈希表,容量不够时rehash,会导致迭代器失效。可通过reserve() 预设容量
  • vector: 也有resize导致迭代器失效的问题。因此标准库的容器都不是线程安全的。
  • 遍历vector比list快,因为cache命中率高
  • emplace()减少一次拷贝

# 智能指针底层原理: https://colderleo.github.io/pages/681d3b/ (opens new window)

  • 多线程中智能指针的引用计数如何统一
  • 多线程中使用智能指针是否是线程安全的

# 网络通信

  • kernel bypass: 智能网卡收包或者发包大概700ns https://colderleo.github.io/pages/85ea0b (opens new window)
  • 常见智能网卡: https://colderleo.github.io/pages/74bbb4 (opens new window)
  • tcp 基础知识: 湖科大教书匠-计算机网络,他的教程特别好

# 对端地址可以ping通,但是connect连接失败提示no route to host,可能是什么原因

1)本机自己开了防火墙 2)本机的etc/hosts 里面没有配置本机的机器名和ip

# epoll中et和lt的区别与实现原理

LT: 水平触发,效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是: 只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。 ET: 边缘触发,效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。 参考: https://blog.csdn.net/ptgood/article/details/106845651

# tcp延迟确认

# systemtap/bpftrace原理和使用

# gdb+汇编

  • 函数开始调用和调用结束时栈指针是怎么变化的

  • 函数调用时参数如何传递(x86下): 前六个参数用寄存器传(6个参数分别为 rdi rsi rdx rcx r8 r9),后面用栈

  • 查看某个地址内存数据: x/nfu n-数量 f-format u-单位 比如 x/20xb 0x400000 x/s addr 打印地址addr处的字符串

  • gdb调试release版的C++程序,如何打断点。 一个release版的so,如何根据反汇编在其某一行代码处打断点

# 大小端和网络字节序

  • 小端机器,发送一个结构体时,网络包中的字节序是大端还是小端。
  • 写一下0x12345678在小端内存中的分布

# 如何在头文件中定义全局变量,被多个cpp包含时而不出错

  • attribute((weak))
  • 在头文件中定义模板类,类中包含static成员变量的声明,类外写成员变量的定义(可初始化)。声明和定义都在头文件中。被多个cpp包含时,不会出现冲突。
  • 定义为static类型,这种做法实际上相当于每个cpp定义一个,多个cpp会存在多个不同的变量,而不是全局变量。
  • 定义为inline类型(c++17), 不要加static,可以加__thread, 如 inline int a = 5;
  • 类内定义为inline static类型(c++17), 类内的static变量本身就是全局唯一的变量,但是之前不能在头文件中定义, c++17之后加上inline就可以了。

# 算法题

  • 用两个队列实现一个栈

# 基础题目

  • 给一个so,怎么用
  • 如何定义一个全局变量
  • 局部static变量如何初始化

# 智力题

  • 有100枚平放在桌上的硬币,10枚正面朝上,90枚反面朝上 不看不摸不能用任何方式区分硬币正面。如何将它们分成两堆 且每堆正面朝上的硬币数目相同(可翻面)

  • 有100个硬币,开始正面朝下,第一次把1的倍数变向,第二次把2的倍数变向,第三次3的倍数,以此类推,进行100次,问朝上的有多少个?

  • 3388算24, 3377算24

  • 有两个一样的玻璃球,从100楼往下扔,判断它恰好会在哪一层碎。请设计一个方案,使得最坏情况所扔的次数最少。

  • 有12个球,其中1个跟其他11个质量不一样,现在有一个天平,称三次,找出这个质量不一样的球。

  • 圆内任取两条弦,两条弦在在圆内相交的概率是1/3

# 模板

  • std::remove_reference_t 是怎么实现的
  • typename的作用
  • std::forward使用场景,为什么不直接使用右值引用

# 问答题

请写出以下代码执行输出结果,并解释为什么?

1. const std::string s1 = "abc";
std::string s2 = std::move(s1);
std::cout << s1 << std::endl;
std::cout << s2 << std::endl;
std::move(s2);
std::cout << s2 << std::endl;
1
2
3
4
5
6
  1. 请写出以下代码执行后,a和b的结果分别是多少?
auto a = -1 << 1;
auto b = -1 >> 1;
1
2
  1. 如何将数组做为参数传递而不会导致类型退化,请写出func的具体实现?
int array[10];
func(array);
1
2

答案:

#include<iostream>
using namespace std;

template<class T>
int func(T& arr)
{
	return sizeof(arr);
}

int main()
{
	int Arr[] = { 1,2,3,4,5,6 };
	cout << func(Arr) << endl;
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
编辑 (opens new window)
上次更新: 2023/05/07, 17:29:19
常见程序性能开销cost、latency延迟耗时的经验值
静态链接-动态链接-elf详解-elfloader

← 常见程序性能开销cost、latency延迟耗时的经验值 静态链接-动态链接-elf详解-elfloader→

最近更新
01
通过模板实现结构体成员拷贝-按成员名匹配
05-07
02
c++17通过模板获取类成员的个数
05-01
03
avx-sse切换惩罚
04-30
更多文章>
Theme by Vdoing | Copyright © 2019-2023 Colder Leo | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×