Kewei
首页·Home
留言·Message
关于·About
搜索
后端面试常见题目及知识点(一)---Linux
2020年09月20日 23:17:25
619
0
0
文章分类:
后端面试
最近正是求职季,自己也经历了多次面试,网上看了许多面经。整个过程中,记录了很多,学到了很多,开个博客,分享给大家,祝大家Offer多多! 该系列博客会从几个不同的大方向,对常见问题进行分类,同时内容为我日常整理方便自己快速回忆,复习,并且为了尽可能覆盖更多的知识点,所以每个问题只有简短关键的回答,如果对相关知识点不熟悉的,建议多查阅一些相关资料。 # 系列文章目录 1. [操作系统(Linux)](https://blog.csdn.net/kewei168/article/details/108690001) 2. [计算机网络](https://blog.csdn.net/kewei168/article/details/108690391) 3. [数据库](https://blog.csdn.net/kewei168/article/details/108700434) 4. [其他](https://blog.csdn.net/kewei168/article/details/108700482) 目录 [TOC] 操作系统常见问题:线程进程相关知识,Linux常规命令等 # Linux常用指令 ## 查看所有进程 `ps -ax | less` * -a代表列出所有进程 * x代表列出没有tty(控制终端)的进程 * | linux指令里面的管道,利用管道将前一个程序的输出导入后一个程序(ps ---> less) 拓展:ps是如何实现的?简述原理 Linux的/proc目录下面包含了所有进程的信息(`ls /proc/`),每个进程都有一个对应的文件夹,名字为进程号。所以实现ps基本原理就是遍历改目录,获取所有的进程信息 ## 文档内查找内容 `cat file.txt | grep test` ## 如何查看操作系统是几位的 `uname -m` --- x86_64 or x86 # 进程,线程,协程 * 进程:**进程是资源分配的最小单位**(分配内存等) * 进程的fork都是先复制自身,然后再修改新进程的内容 ---> init是所有进程的“老祖宗” * 线程:**线程是CPU调度的最小单位**(CPU “fetch”一个线程,然后执行) * 协程:“用户态的线程”,协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行(以同步的方式,在一个线程内执行任务) * 进程 v.s. 线程 * 每个进程最少包含一个线程,每个线程必定属于某个进程 * 进程要比线程消耗更多的计算机资源,不同进程间的隔离度也更高(i.e. 一个进程终止一般不会影响到另一个进程),但是一个线程终止可能会导致该线程所属的整个进程同时终止 * 多进程:允许多个任务同时执行 * 多线程:允许一个任务的不同部分同时执行(embarrassingly Parallel) * 进程内部所有线程共享内存空间 * 线程 v.s. 协程 * 线程切换:操作系统完成,overhead较大(e.g. 保存当前寄存器,flush,重新加载) * 协程切换:用户完成,overhead较小(还是在同一个线程内) ## 进程的几种状态 * R(TASK_RUNNING)--- 可执行状态 * ready和running都属于这个状态 * S(TASK_INTERRUPTIBLE) --- 可中断的睡眠状态 * 处于该状态的进程,通常由于等待某事件的发生(如socket),而被挂起(task_struct被放入对应事件的等待队列中); * 对应事件发生时,对应进程会被唤醒 * 如果使用ps查看进程,会有很多进程处于该状态(CPU能承受多进程的原因!) * D(TASK_UNINTERRUPTIBLE) --- 不可中断的睡眠状态 * 存在的意义是有些系统操作是不可打断的,如操作系统对某些硬件进行交互的时候,需要用这个进行保护(kill信号无法杀死这个状态的进程) * T(TASK_STOPPED or TASK_TRACED) --- 暂停状态或跟踪状态 * 如断点调试 * Z(TASK_ZOMBIE) --- 退出状态,进程成为僵尸进程 * 进程在退出过程中,所有资源都被回收,除了task_struct结构(和少数资源)外,此时线程处于僵尸进程 * 父进程可以通过wait来使得僵尸进程退出 * X(TASK_DEAD) --- 退出状态,进程即将被销毁 * 该状态通常很短暂(接下来就会销毁该进程),ps一般看不到 ## 线程的几种状态 * new --- 创建 * ready --- 创建成功,可以被执行 * running --- 正在执行 * waiting --- 等待,由于IO或者有更高优先级的事情(如处理中断) * terminated --- 结束,释放资源  ## 为什么需要协程? 1. 节省CPU,协程是用户态的线程,不存在线程切换的开销; 2. 节约内存,系统会给每个线程分配一定大小的栈内存(e.g. 8M)和堆内存(64M),线程数量有瓶颈;而协程的栈通常只有十几K,并且是从线程的堆里面分配出来的,数量受限小; 3. 稳定性,不存在线程安全问题,因为协程是在一个线程内部,以**同步**的方式读写数据; 4. 开发效率,可以利用协程将一些耗时操作异步化; ## 线程(进程)间的数据共享/通信 * 线程间通信 * 共享内存,共享变量,更需要考虑的是race condition * 进程间通信 * 管道(pipe),单向通信,只能在具有亲属关系(多为父子关系)的进程间使用 * 命名管道(FIFO),可在任意两个进程间使用 * 信号量(semophore),一个计数器,多用于控制多进程访问共享资源 * 消息队列(message queue),储存在内核中的一个消息链表 * 信号(signal),类似于中断的概念 * 共享内存(shared memory) * **socket**!!!(甚至可以做到分布式) ## 进程有哪些字段 * PID * Stack * Heap * Execution context * Program counter (PC) * Stack pointer (SP) * 寄存器(Registers) * 代码 * 数据 * 独立内存空间 ## 多线程如何做到thread-safe * thread local变量(直接避免了race condition) * 原子量(atomic)和原子操作(test-and-set,compare-and-swap) * 临界区(critical section) * 互斥锁(mutex) * 信号量(semaphore) * 事件对象(event) ## 活锁 V.S. 死锁 * 死锁:两个或两个以上的线程,因争夺资源而陷入互相等待(阻塞),会一直持续下去 * 可能是由于多个线程加锁的顺序不一样 * 活锁:线程在执行,但是由于某些条件未满足,所以都是无用功 * 形象的比喻,死锁是lock,活锁是try_lock(一直try),两者都是没有进度,但死锁是阻塞状态,活锁是运行状态 * 饥饿:某个线程“长期”处于等待资源状态,比如由于优先级不够高,系统一直不执行 ## 僵尸进程 V.S. 孤儿进程 * 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用`wait`或`waitpid`获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程 * 任何一个进程退出后都会经历“僵尸进程”这个阶段,只有等到父进程调用`wait`或者`waitpid之后`,进程才会真正结束 * 大量的僵尸进程可能会耗尽系统的进程号资源 * 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(pid=1)所收养,并由init进程对它们完成状态收集工作 * 孤儿进程不会有什么危害,因为孤儿进程会被init“收养”并正常退出 * 如何避免僵尸进程? * 1)通过信号机制;子进程退出时会向父进程发送SIGCHILD信号,父进程可以捕捉这个信号进行`wait`处理 * 2)fork两次(将子进程变为孤儿进程);先fork一次得到子进程1,然后在1里面再fork一次,成功后将1结束,从而使得子进程2变为孤儿进程,并被init进程“收养” # 阻塞 V.S. 非阻塞,异步 V.S. 同步 * 阻塞:指调用结果返回之前,调用者会进入阻塞状态(线程状态,此时该线程可能会进入wait状态)等待。只有在得到结果之后才会返回(e.g. socket的recv函数) * 非阻塞:指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回(e.g. socket的send函数,数据写到缓冲区之后立刻返回) * 同步:在发出一个同步调用时,在没有得到结果之前,该调用就不返回(程序状态,如调用一个函数,此时该线程还可以在CPU上运行) * 异步:在发出一个异步调用后,调用者不会立刻得到结果,该调用就返回了 * 同步 V.S. 阻塞 * 表面上看都是“卡住了”,但是阻塞指的是该线程被阻塞(CPU不再运行该线程);同步指的是程序/调用被阻塞,但是该线程可能还在CPU上面运行 * 两两组合 * 同步阻塞调用:得不到结果不返回,线程进入阻塞态等待; * 同步非阻塞调用:得不到结果不返回,线程不阻塞一直在CPU运行; * 异步阻塞调用:去到别的线程,让别的线程阻塞起来等待结果,自己不阻塞; * 异步非阻塞调用:去到别的线程,别的线程一直在运行,直到得出结果; # 大端,小端和名字的由来 * 名字由来 --- 格列佛游记 * 大端 --- 数据的高字节保存在内存的低地址中 * 小端 --- 数据的低字节 # 字节对齐 * 基本的数据类型对齐,int 4,char 1,double 8... * 结构体,类的长度对齐 * 原因:CPU加载数据常常是从对齐地址开始加载的;CPU一次不是加载1byte,而是多byte # memcpy和memmove * memcpy比memmove效率更高,更快 * memcpy不会检查有无地址有无重叠,所以可能会出错 * memmove会检查地址有无重叠,若有重叠会把src先拷贝到tmp再拷贝到dst * 实现: ```cpp void *memcpy(void *dest, const void *src, size_t n) { // 注意要先转换为char* char * srcC = (char*) src; char * dstC = (char*) dest; while (n >= 0) { *srcC++ = *dstC++; } return dest; } ``` # 堆 vs 栈 * 分配方式不同 --- 堆,程序申请,自行管理;栈,操作系统分配 * 管理方式不同 --- 堆,程序管理;栈,操作系统管理 * 运行效率不同 --- 栈的效率比堆高
点赞
0
分享