操作系统 Flashcards
什么是僵尸进程,什么是孤儿进程
在正常情况下,一个父进程创建了一个子进程,当这个子进程调用exit或者运行时发生致命错误或者收到了终止信号,它就会终止,然后它的返回值会回报给操作系统,然后操作系统会用SIGCHILD信号通知父进程子进程已经终止了,父进程通过wait或者waitpid获取子进程的终止状态,然后系统内核从内存中释放已结束的子进程的PCB
上面是正常情况下子进程退出时的流程,但是有的时候会出现两个问题,一个是孤儿进程,一个是僵尸进程
孤儿进程就是父进程先退出,但是它还有一个或者多个子进程还在运行,那么这些子进程就成了孤儿进程,孤儿进程将被init进程,也就是进程号为1的进程收养,然后当它们退出的时候退出状态会由init进程收集,所以孤儿进程并不会造成太大的危害
僵尸进程就是当子进程退出后,操作系统用SIGCHILD信号通知父进程,此时子进程虽然已经结束但是它的进程控制块也就是PCB还留在内存中,父进程收到了SIGCHILD信号却没有使用wait或者waitpid来获取子进程的退出状态,就导致操作系统一直没有把子进程的PCB从内存中释放掉,此时子进程的状态通过ps -aux查看发现是Z,就是zombie,也就是僵尸进程了。僵尸进程是有危害的,因为父进程没有获取退出信息,它的PCB所占用的空间也就不会被释放,因为我们知道PCB是个结构体会占用一些空间,这就造成了资源浪费
如何解决僵尸进程的问题
主要分为两种方式,清理僵尸进程和避免僵尸进程
清理僵尸进程不能直接用kill,用kill -9也杀不掉,但是可以用kill杀死僵尸进程的父进程,这样一来僵尸进程就会变成孤儿进程,由init进程收养,init进程就会调用wait(),然后就会将其释放。还有就是重启服务器
避免僵尸进程,也是由两种方法,一种是修改SIGCHILD信号的信号处理函数,可以在信号处理函数中调用wait获取进程结束状态,也可以用signal函数把SIGCHILD信号的处理方式改成SIG_IGN,这样就向系统申明了不关注子进程的退出状态,子进程退出后系统就不等待父进程操作而是直接回收这个子进程的资源。还有一种办法就是父进程创建子进程的时候连续调用两次fork(),并且让第一代子进程直接退出,并且用waitpid()给它收尸,这样第二代子进程的父进程就直接变成了init进程,它退出的时候就由init进程处理,这样也就不会出现僵尸进程了
什么是大端字节序,什么是小端字节序
在计算机系统中,以字节为存储单元,每个地址单元对应一个字节,一个字节是八个比特位,而在编程语言中,不仅用一个字节来存储一个数据,除了一个字节的char还有short、int等等多于一个字节的数据类型,这样的话,就出现了多个字节如何排布的问题,于是就有两种排布方式,一种是大端字节序,一种是小端字节序
大端存储模式,就是指数据的低位保存在内存的高地址中,小端存储模式,是指数据的低位保存在内存的低地址中
如何测试电脑是大端模式还是小端模式
两种方法:
第一种,定义一个int变量值等于1,然后用一个int类型的指针指向它,再强转成char类型,然后解引用输出,如果是1就代表低位存在低地址,所以是小端序,如果是0代表高位存在低地址,所以是大端序
第二种,定义一个联合体,一个int成员,一个char成员,给int成员赋个1,然后输出char成员的值,如果是0代表高位存在低地址,就是大端序,是1表示低位存在低地址,就是小端序
vim如何查找一个字符串
普通模式下,按斜杠键,输入想要查找的字符串,按回车,就可以向下查找字符串,按问号键,输入想查找的字符串,再按回车,就是向上查找字符串,然后按小写n是向下找下一个,按大写N是向上找下一个
讲一下vim替换操作
普通模式按冒号键进入命令模式,然后输入s斜杠斜杠斜杠g,前两个斜杠中间填要被替换的字符串,后两个斜杠中间填要替换成的字符串,这就是替换当前行的所有匹配到的字符串,去掉g就只替换当前行的第一个,还可以在s前面加上行号逗号行号,就是替换这两个行号之间的匹配到的字符,或者在s前面加百分号,就是替换全文匹配到的字符
讲一下gdb和调试的一般过程和指令
gdb,全称GNU Debugger,是unix环境下的调试工具,它可以用于调试多种语言的程序,但是一般多用于调试C和C++的程序。
一般在使用gdb进行调试的时候,首先要在使用gcc进行编译的时候加上-g参数,这样会保留调试信息,如果不加-g参数,会发现gdb命令也可以运行,但是只能通过run指令跑完程序,查看代码、变量、打断点这些功能都不能正常执行了,这是因为gcc编译不加-g的时候默认编译出的文件是realese版本,也就是发行版,不带调试信息。编译完成之后就可以通过gdb加文件名的命令开始调试,调试的主要命令有:list-显示10行源代码,break-设置断点,run-运行程序,next-单步运行不进入函数,step-单步运行进入函数,print-打印变量的值,continue-继续运行直到下一个断点,等等。上面说的这些指令都可以简写成首字母。还有一个常用的是where可以看到函数的调用栈。
GDB调试core文件
程序在一些情况下发生崩溃的时候会发生coredump,产生一个core文件,里面包含了程序运行时的内存、寄存器、堆栈、函数调用等信息,我们可以使用GDB去分析core文件,从而找到程序异常退出的问题所在。core文件一般默认是保存在和可执行程序的同一目录,但是在代码中可以对core文件保存的目录进行修改。但是首先,要通过ulimit -c命令查看当前环境是否会生成core文件,如果发现当前环境限制了core文件的大小为0,还要用ulimit -c unlimited命令更改设置才能生成core文件。设置好了之后如果程序运行出现野指针、内存越界、多线程可重入函数出错、多线程数据未加锁,这些段错误的话,就会提示segmentation falut(core dumped),这就发生了核心转储。之后就可以用gdb对core文件进行调试,主要就是用bt(backtrace)或者where查看函数调用栈,就可以知道是在哪个函数中发生的错误,然后就可以定位到问题的根源,然后再去看具体函数的源代码,查找并解决问题
GDB 调试已运行程序
首先使用 ps -ef | grep 进程名 命令找到进程id,然后命令行输入gdb,进入gdb命令行,然后使用attach加进程id,然后就可以调试运行中的程序了
调试多线程
调试多线程的话,基础步骤和命令跟调试单线程程序一样,大概就是编译的时候加上-g,然后就用gdb加可执行文件名命令进入gdb调试,然后多线程调试的主要命令有info threads–显示当前所有线程,每个线程会有一个gdb分配的ID,thread + 线程ID–就可以切换当前调试的线程为指定ID的线程,thread apply + 线程ID号 + 命令–让多个线程执行命令,还有就是set scheduler-locking +off/on/step,就是设置单步执行调试的时候别的线程是否会同时执行,off就是所有线程都运行,on就是锁定,只有当前线程运行,step就是单步的时候锁定,只有当前线程可以执行,非单步的调试不锁定,都可以执行
编译链接的过程
一个C++源文件,从文本到可执行文件一般需要四个过程,分别是预处理、编译、汇编、链接,如果有一个叫做main.cpp的源文件,它首先经过预处理器会翻译成一个main.i的中间文件,之后编译器会将它翻译成一个汇编语言文件main.s,之后汇编器将它翻译成一个可重定位目标文件main.o,再之后链接器将它和用户生成的其它可重定位目标文件还有系统目标文件组合起来,就形成了一个可执行目标文件
具体来说,
预处理阶段:
主要处理源代码中”#”开头的指令,比如#include、#define、#if、#endif等等,除了#pragma的指令是留给编译阶段处理的,还会删除代码中的注释等等
编译阶段:
对预处理后生成的.i文件进行词法、语法、语义的优化,优化之后生成相应的汇编代码
汇编阶段:
将汇编代码翻译成机器码,生成一个可重定位目标文件
链接阶段:
把所有可重定位目标文件进行分段的合并,其中符号表合并后,进行符号解析,然后将符号进行重定位,比如说我main.cpp里有个sum函数的声明,这个函数在sum.cpp里实现,那么sum函数在main.cpp里也会产生符号,但是找不到它的地址,当进行了上述过程之后,就只剩一个带有地址的符号了。最终链接器会把多个目标文件和静态库连接成最终的可执行目标文件
静态链接库和动态链接库
静态库
静态库就是静态链接库,在Windows下后缀是.lib,在Linux下后缀是.a,静态链接在源代码生成可执行程序的四个阶段中的链接阶段进行,它会把库文件中所有的内容加入到可执行文件中
两个缺点,一个优点:
这样就会导致两个问题,一个是空间浪费,因为每当编译一个静态库就把该静态库复制了一遍,当多个可执行程序用同一个静态库时可能会导致内存里有许多相同的代码复制了很多份;还有一个问题就是如果我们更改了静态库里的一点点代码,也需要重新进行一遍编译链接的过程,许多大型工程编译一遍需要很长时间,这样效率不高
但是静态链接库也有优点,就是可执行文件包含了库里的内容,在执行可执行文件时就不需要加载了,运行速度会比较快
动态库
动态库就是动态链接库,在Windows下后缀时.dll,Linux下是.so,与静态库相反,动态库在编译时并不会被拷贝到可执行文件里,可执行文件里只会存储指向动态库的引用,等到程序运行时才会把动态库加载进来
两个优点,一个缺点:
因为不会拷贝到程序里,也就不会影响程序的提及,节省了空间,而且可以同一份库被多个程序使用,所以动态库也可以叫做共享库。而且由于动态库在程序运行时才会被载入,所以进行对库的更新或者替换操作的时候不需要把整个程序都重新编译链接一遍,但是也会带来一个问题就是动态库把链接推迟到了程序运行的时候,所以每次执行程序都要进行链接,性能上会有损失
什么是进程
首先,通俗的说,进程就是运行中的程序。在计算机中,操作系统管理着所有的软硬件资源,一般操作系统会通过一种“先描述,再组织”的方式对资源进行管理,也就是说操作系统首先要把进程这个抽象的概念用计算机的方式描述出来,然后才可以对这些不再抽象了的信息进行组织。而操作系统对进程的描述,就是PCB(process control block),在Linux下,PCB是一个名叫task_struct的结构体。在这个结构体中,保存了一些例如标识符、记录进程状态的信息、记录进程即将执行的下一条指令的信息等等这些信息。通过PCB和内存中进程对应的代码与数据,就真正的组成了一个进程的实体。
如何查看一个进程
第一种方式,可以在/proc系统文件夹查看,在这个文件夹内可以通过pid找到对应的进程
第二种方式,通过ps命令查看,常用的参数有ps -aux和ps -ef,ps -aux显示的内容比较全,会包括进程状态、进程占用的内存这些,但是ps -ef能显示进程的父进程的pid。一般在使用的时候我们会在ps命令后面加上个管道,grep加进程pid或者进程名来筛选出要找的进程
第三种方式,可以用top命令动态地显示系统当前所有进程
什么是守护进程
守护进程,英文是deamon,又叫精灵进程,这种进程的名称一般用字母d结尾,它在后台运行,不受终端控制,终端退出的时候它不会退出,当服务器关闭的时候它才会退出,所以一般网络服务会以守护进程的方式运行,它也是一个特殊的孤儿进程,它的父进程是init进程