北京企业网站模板建站开发下载了wordpress后

当前位置: 首页 > news >正文

北京企业网站模板建站开发,下载了wordpress后,wordpress主题没有评论,优设网官网教程目录

  1. 内核态和用户态 1.1 内核态和用户态概念 1.2 内核态和用户态转化
  2. 处理信号 2.2 捕捉信号 2.2 系统调用sigaction
  3. 不可重入函数
  4. volatile关键字
  5. SIGCHLD信号#xff08;了解#xff09;
  6. 笔试选择题 答案及解析 本篇完。 1. 内核态和用户态…目录
  7. 内核态和用户态 1.1 内核态和用户态概念 1.2 内核态和用户态转化 2. 处理信号 2.2 捕捉信号 2.2 系统调用sigaction
  8. 不可重入函数
  9. volatile关键字
  10. SIGCHLD信号了解
  11. 笔试选择题 答案及解析 本篇完。 1. 内核态和用户态 前一篇问到信号的处理是否是立即处理的在合适的时候才处理。 合适的时候是什么时候这首先就要先看看什么是内核态和用户态 1.1 内核态和用户态概念 用户态正在执行用户层的代码此时CPU的状态是用户态。内核态正在通过系统调用访问内核或者硬件资源时此时CPU的状态是内核态。 用户为了访问内核或者硬件资源必须通过系统调用才能完成访问。虽然系统调用是在我们的代码中写的也就是用户在使用但是具体的执行者是内核也就是操作系统。现在是知道了什么是用户态什么是内核态但是操作系统是怎么知道当前进程的身份状态的呢 CPU中的寄存器虽然只有一套但是有很多有可见寄存器如eaxebx等等还有很多的不可见寄存器凡是和当前进程强相关的都属于当前进程的上下文数据。 如上图有专门用来存放当前进程PCB指针的寄存器。也有专门存放当前进程页表指针的寄存器。 CR3寄存器专门用来表征当前进程的运行级别的。 0表示内核态此时访问的是内核资源或者硬件。 3表示用户态此时执行的是用户层的代码。 操作系统是一个进行软硬件资源管理的软件它很容易就可以获取到CPU中CR3寄存器中是0还是3从而知道当前是用户态还是内核态。 执行系统调用时执行者是操作系统而不是用户。那么又存在一个问题一个进程是怎么跑到操作系统中执行代码的呢 对进程地址空间进行一个补充介绍 我们之前一直所说的页表都是用户级页表每个进程都有一个。 进程地址空间的大小一共有4GB我们之前谈论的只有0~3GB这3GB的空间属于用户空间用来存放用户的代码数据等。为了保证进程的独立性每个进程都有一个进程地址空间都有一个用户级页表。 还有一共内核级页表所有进程共用一份。 进程地址空间中的3~4GB空间是不允许用户访问的因为这1GB空间中的数据等通过内核级页表和内存中的操作系统相映射属于内核级别的。因为内存中只存在一份内核所以所有进程的虚拟地址空间的这1GB空间都通过同一份内核级页表和内存中的内核相映射。 每一个进程地址空间中的3~4GB的内容都是一样的因为它们都通过同一个内核级页表和内存中的内核相映射。 还记得动态链接吗通过代码段的位置无关码跳转到共享区从内存中映射过来的动态库来执行相应的方法。系统调用和它的原理一样 当执行到代码段中的系统调用时会在跳转到当前进程虚拟地址空间中的内核空间中。 系统调用的具体实现都放在这1GB的内核空间中。 然后根据内核级页表和内存中内核的映射关系实现内核的访问。 1.2 内核态和用户态转化 此时又有一个问题为什么我们的代码中不能访问这3~4GB的空间而系统调用就跳转到这1GB的内核空间中进行访问了呢我们都是用户的代码啊 因为从代码段跳转到内核空间中后CPU中的CR3寄存器从3变成了0。意味着进程运行级别从用户态变成了内核态也就是执行者从用户变成了操作系统所以可以对这1GB的内核空间进行访问。 系统调用接口的起始位置会将CR3寄存器中的数据从3变成0完成从用户态向内核态的转变。 所以说系统调用前一部分是由用户在执行其余部分由操作系执行。 此时再来理解信号处理的时机从内核态返回到用户态这句话的含义必然曾经进入到了内核态而进入内核态的方式很多比如进程切换只有操作系统才有权力将进程从CPU上剥离下来换上另一个进程。还有系统调用等等方式。 以我们最熟悉的系统调用为例 以黑色长线为界上面是用户态下面是内核态。 当执行到用户代码段中的系统调用时会跳转掉虚拟地址空间中的内核空间去执行具体的方法此时从用户态变成了内核态。 当系统调用被操作系统执行完毕以后在返回之前操作系统会检测task_struct中block位图pending位图然后再根据handler中的处理方式去处理相应的信号。 如是自定义处理方式操作系统会拿着handler表中的函数地址通过特定的系统调用去执行用户自定义的处理方式此时从内核态变成了用户态。在执行完自定义处理方式以后再次回到内核中取系统调用得到的数据此时再次从用户态变成了内核态。 拿上要取的数据以后通过特定的系统调用返回到用户代码中系统调用的位置再次从内核态变成了用户态。 上面过程的伪代码形式 涉及到的系统调用无需详细了解只需要知道是通过系统调用实现的即可。 上面过程中存在一个问题在执行自定义处理方式的时候为什么必须从内核态切换成用户态去执行用户定义的处理方式呢不能直接以内核态的身份去执行吗 不可以。理论上是绝对可以实现的因为内核态比用户态高高级别去处理低级别肯定是可以的。但是操作系统不相信任何人如果自定义处理方式中有用户的恶意代码而此时又以操作系统身份去执行那么就会导致问题。所以必须得切换到用户身份去执行自定义处理方式才能保证系统的安全。 两个独立的流程 此时就存在了两个流程一个是main函数所在的执行流程一个是自定义处理方式的执行流程 在执行完系统调用后不是恢复main函数的上下文进行执行而是执行用户自定义的处理方式。自定义处理方式函数和main函数使用不同的堆栈空间并且不存在调用和被调用的关系是两个独立的控制流程。 上面整个过程可以看成一个无穷大符号加一条线线的上边是用户态下边是内核态。每经过一次黑线就会发生一次身份状态的改变一共改变了四次。 这个图面试画出来并能讲解的话上大分 上面这种自定义处理方式是最复杂的情况如果是SIG_DFL(默认处理方式)和SIG_IGN(忽略方式)以内核态身份就可以处理然后就可以直接返回到用户代码中系统调用的位置少了两次身份的转变。因为默认方式和忽略方式是被写入到操作系统中的被操作系统所信任的方式。 默认处理方式所有信号的默认处理方式都是结束进程只是不同信号表示不同的异常。忽略处理方式忽略和阻塞不一样忽略也是一种处理方式它仅仅是将task_struct中的pending位图中对应信号的比特位清空然后就直接返回到用户态了。 2. 处理信号 内核如何实现信号的捕捉再看这张图 2.2 捕捉信号 如果信号的处理动作是用户自定义函数 在信号递达时就调用这个函数, 这称为捕捉信号。 由于信号处理函数的代码是在用户空间的 处理过程比较复杂 举例如下 用户程序注册了 SIGQUIT 信号的处理函数 sighandler 。 当前正在执行 main函数 这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的 main 函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复 main 函数的上下文继续执行 而是执行 sighandler 函数 ighandler和main 函数使用不同的堆栈空间它们之间不存在调用和被调用的关系 是两个独立的控制流程。 sighandler 函数返回后自动执行特殊的系统调用sigreturn 再次进入内核态。 如果没有新的信号要递达 这次再返回用户态就是恢复 main函数的上下文继续执行了。 捕捉信号和内核态和用户态关系很大。 2.2 系统调用sigaction sigaction函数可以读取和修改与指定信号相关联的处理动作。man sigaction int signum信号编号。act这是一个结构体变量这个结构体名字和函数名一样不建议这么用但是这里这样用了结构体中包括多个属性sa_handler赋值自定义处理方式暂时将sa_flags都设为0其他暂时不用管。oldact是一个输出型的结构体变量将原本的捕捉方式放入这个结构体变量中。返回值成功返回0失败返回-1。 当某个信号的处理函数被调用时内核自动将当前信号加入进程的信号屏蔽字当信号处理函数返回时自动恢复原来的信号屏蔽字这样就保证了在处理某个信号时如果这种信号再次产生那么它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时除了当前信号被自动屏蔽之外还希望自动屏蔽另外一些信号则用sa_mask字段说明这些需要额外屏蔽的信号当信号处理函数返回时自动恢复原来的信号屏蔽字。 sa_flags字段包含一些选项这里的代码都把sa_flags设为0sa_sigaction是实时信号的处理函数这里不详细解释这两个字段。 这里创建个Linux_20在里面写代码捕捉一下2号信号 Makefile-fpermissive是取消下面代码强转报错 mysignal:mysignal.ccg -o \( \)^ -stdc11 -fpermissive .PHONY:clean clean:rm -f mysignal mysignal.cc #include iostream #include unistd.h #include signal.h using namespace std;void handler(int signum) {cout 获取了一个信号 signum endl; } int main() {signal(2, SIG_IGN);cout getpid: getpid() endl;// 内核数据类型用户栈定义的struct sigaction act, oact;act.sa_flags 0; // 把这个赋为0,其它不用管sigemptyset(act.sa_mask); // 清空act.sa_handler handler;// 设置进当前调用进程的pcb中sigaction(2, act, oact);cout default action : (int)(oact.sa_handler) endl;while (true){sleep(1);}return 0; } 在handler里睡眠10秒 在进程开始运行后我们在10s内发送了很多次2号信号但发现只能10秒捕捉一次。 当递达第一个2号信号的时候同类型的信号无法被递达。 因为当前信号在被捕捉的时候系统会自动将当前信号加入到进程的信号屏蔽字也就是将block对应的比特位置位然后将pending表对应比特位清空再去进行递达。 但是第二个2号信号在第一个信号被捕捉的时候会将对应pending位图的比特位置位。 所以当第一个2号信号处理完毕以后解除对2号信号的屏蔽后第二个2号信号就会被递达。除了这两个2号信号其余的2号信号都被舍弃了。 进程处理信号的原则是串行的处理同类型的信号不允许递归所以同类型的多个信号同时产生最多可以处理两个。上面内容系统调用signal也可以实现那么sigaction相对于signal有什么优势呢刚刚代码中由于在2号信号的自定义处理中没有结束进程所以只能用其他信号来结束这个进程如上面使用的是3号信号如果想要在捕获2号信号以后将3号信号也屏蔽了呢此时就需要设置结构体变量act中的sa_mask成员。 再加一些代码演示一下 #include iostream #include unistd.h #include signal.h using namespace std;void showPending(sigset_t *pending) {for(int sig 1; sig 31; sig){if(sigismember(pending, sig)) cout 1;else cout 0;}cout endl; } void handler(int signum) {cout 获取了一个信号 signum endl;sigset_t pending;int c 20;while(true){sigpending(pending);showPending(pending);c–;if(c 0){break;}sleep(1);} } int main() {signal(2, SIG_IGN);cout getpid: getpid() endl;// 内核数据类型用户栈定义的struct sigaction act, oact;act.sa_flags 0; // 把这个赋为0,其它不用管sigemptyset(act.sa_mask); // 清空act.sa_handler handler;sigaddset(act.sa_mask, 3); // 处理2号信号期间随便把一些信号屏蔽sigaddset(act.sa_mask, 4);sigaddset(act.sa_mask, 5);sigaddset(act.sa_mask, 6);sigaddset(act.sa_mask, 7);sigaddset(act.sa_mask, 8);// 设置进当前调用进程的pcb中sigaction(2, act, oact);cout default action : (int)(oact.sa_handler) endl;while (true){sleep(1);}return 0; } 如果只用2号信号和3号信号演示 当第一次2号信号被捕获后第二个2号信号虽然被阻塞了但是它还是让pending位图置位了。 当第一次2号信号被递达完成后就会递达第二个2号信号。 当第二次2号信号被递达完成后2号信号的pending位图的比特位是0所以才递达3号信号。 虽然在捕获2号信号的同时会阻塞3号信号但是3号的pending位图的比特位仍然被置位了。 在第一个2号信号被捕获的时候同时阻塞了第二个2号信号和3号信号此时pending位图的第二个和第三个比特位都是1但是当第一个2号信号递达完成后先处理的是第二个2号信号而不是3号信号。 一般一个信号被解除屏蔽的时候会自动递达这个信号如果该信号pending位图的比特位是1的话就会递达是0的话就不做任何处理。 3. 不可重入函数 如上图所示链表在插入节点的时候捕获到了信号并且该信号的自定义处理方式中也调用了插入节点的函数。 在main函数中使用insert向链表中插入一个节点node1在执行insert的时刚让头节点指向node1以后(如上图序号1)捕获到了信号进入到了该信号的自定义处理方式中。 在自定义处理方式中同样调用了insert函数向链表中插入一个节点node2此时完整的执行了insert函数但是在头节点和最开始那个节点之间同时有了node1和node2(如上图序号2和3)。 当第二次调用insert中让头节点指向node2后(如上图序号3)流程返回到信号的自定义处理函数中然后再返回到第一次调用insert处头节点指向node1(如上图序号4)。 最后可以看到该链表是丢了一个节点的。 重入像insert函数这样在main流程中调用还没有返回时就再次被handler流程调用再次进入该函数。 insert函数访问的是一个全局链表有可能会因为重入和造成错乱像insert这样的函数就称为不可重入函数。如果一个函数只访问自己的局部变量或参数则不会造成错乱此时这样的函数就称为可重入函数。 注意 可重入和不可重入是函数的特性是中性的并不是问题所以也不需要被解决。我们目前使用的大部分结构都是不可以重入函数。符合以下条件之一的就是不可重入函数 调用了malloc或者free因为malloc也是用全局链表来管理堆的。调用了标准I/O库函数标准I/O库的很多实现都以不可重入的方式使用全局数据结构。 4. volatile关键字 看段代码 Makefile mysignal:mysignal.ccg -o \( \)^ -stdc11 .PHONY:clean clean:rm -f mysignal mysignal.cc 定义全局变量quit当quit是0的时候一直进行while循环当quit变成1的时候结束循环进程正常退出。信号2注册自定义处理方式在函数中将全局变量改成1让main函数控制的流程正常结束。 #include iostream #include unistd.h #include signal.h using namespace std;int quit 0; void change_quit(int signum) {(void)signum;cout change quit: quit;quit 1;cout - quit endl; } int main() {signal(2, change_quit);while(!quit);{cout 进程正常退出后 quit endl;}return 0; } 在接收到2号信号后quit从0变成1所以main流程也正常结束了不再循环。 我们的编译器会进行很多的优化比如debug版本和relase版本中的assert就会被优化。在使用g编译器的时候可以指定g的优化级别。-O3是最高的优化级别是大写O字母 Makefile mysignal:mysignal.ccg -o \( \)^ -stdc11 -O3 .PHONY:clean clean:rm -f mysignal 重新编译运行上面代码; 为什么发送2号信号没有退出 此时可以肯定quit被改成了1但是while(!quit)还是在循环没有停下来。 上诉现象的原因是什么肯定是和优化有关因为我们加了-O3选项。 quit在物理内存中一定有一块空间最开始是0。当CPU指向while(!quit)指令的时候会通过虚拟地址和页表的映射将物理内存中的quit数据取到CPU的寄存器中。当quit被修改后物理空间中的数据就会从0变成1。 在没有优化前CPU每次都是从物理内存中拿到quit的数据再去指向while循环所以当quit从0变成1后CPU中寄存器的数据也会及时从0变成1所以while循环会停下来。 但是采用优化方案后在main控制的执行流中quit没有进行修改也没有写入只是被读取所以在第一次将从物理空间读取到寄存器中便不再读取了每次执行while时候都是使用的寄存器中的quit值所以始终都是0。在handler执行流中对quit进行了修改所以物理内存中的quit从0变成了1。 导致上面现象的原因就是CPU执行while时的quit和物理内存中的quit不是一个值。 为了让CPU的寄存器每次都从物理内存中取数据使用volatile关键字来修饰这个quit变量。 给英语不好的铁汁贴个图我可不说我英语不好.jpg volatile作用保持内存的可见性告知编译器被该关键字修饰的变量不允许被优化对该变量的任何操作都必须在真实的内存中进行操作。 编译运行 可以看到此时在handler的执行流中修改了quit值并且CPU中该值也得到了及时更新所以程序可以正常结束。 5. SIGCHLD信号了解 在学习进程控制的时候使用wait和waitpid系统调用何以回收僵尸进程父进程可以阻塞等待也可以非阻塞等待采用轮询的方式不停查询子进程是否退出。 采用阻塞式等待父进程就被阻塞了什么都干不了只能等子进程退出。采用非阻塞式等待父进程在干自己事的同时还要时不时的轮询一下程序实现比较复杂。 实际上子进程的退出并不是悄无声息的在子进程退出时会发出SIGCHLD信号给父进程。 写段代码证明一下在子进程退出时会发出SIGCHLD信号给父进程mysignal.cc #include iostream #include unistd.h #include signal.h using namespace std;void handler(int signum) {cout 子进程退出: signum father pid: getpid() endl; } int main() {signal(SIGCHLD, handler);if(fork() 0){cout child pid: getpid() endl;sleep(7); // 7秒后子进程退出exit(0);}while(true) sleep(1);return 0; } 可以看到子进程在退出时发出了编号为17的SIGCHLD信号被父进程捕捉到了。 如果子进程睡眠很久然后子进程发送19号暂停信号子进程也会发出编号为17的SIGCHLD信号还是证明一下 以前学的当子进程退出后父进程什么都没有干子进程就会变成僵尸状态。 如果有10个子进程都退出呢 - while(wait()) 如果有10个子进程有5个子进程退出呢 - vectorint pids; 如果我们不想等待子进程并且还想让子进程退出之后自动释放僵尸子进程呢 #include iostream #include unistd.h #include signal.h using namespace std;// 如果我们不想等待子进程并且还想让子进程退出之后自动释放僵尸子进程 int main() {signal(SIGCHLD, SIG_IGN); // 手动设置对子进程进行忽略// 和OS默认是忽略的的不一样 - 该僵尸就僵尸if(fork() 0){cout child: getpid() endl;sleep(7);cout 子进程已经退出 endl;exit(0);}while(true) // 父进程不关心子进程{cout father: getpid() 执行自己的任务 endl;sleep(1);}return 0; } man 7 signal下滑看到17号SIGCHLD信号Ign就是ignore忽略的意思 当显式SIGCHLD信号使用忽略方式(SIG_IGN)时退出的子进程就会被自动回收。 虽然SIGCHLD默认的处理方式就是忽略但是默认的忽略不会回收子进程只有显式注册为SIG_IGN(忽略)方式才会自动回收退出的子进程。 6. 笔试选择题
  12. 以下哪些信号无法被阻塞 [多选] A.SIGKILL B.SIGINT C.SIGSTOP D.SIGQUIT 2. 以下描述正确的有[多选] A.使用kill -l命令可以查看Linux系统中信号的种类 B.使用kill -p命令可以查看Linux系统种的信号种类 C.使用CtrlC会使当前终端前台进程退出 D.使用CtrlZ会使当前终端前台进程退出 3. 以下描述正确的有  A.未决信号指的是已经被处理的信号 B.未决信号指的是还未被处理的信号 C.同一个信号可以在未决信号集合中添加多次 D.每一个信号处理完毕后都会从pending集合中移除 4. 以下描述正确的有[多选] A.若信号被阻塞则信号将无法添加到未决信号集合中 B.若信号被阻塞则信号依然可以添加到未决信号集合中 C.若信号被忽略则信号将无法添加到未决信号集合中 D.若信号被忽略则信号依然可以添加到未决信号集合中 5. 一个进程无法被kill杀死的可能有哪些[多选] A.这个进程阻塞了信号 B.用户有可能自定义了信号的处理方式 C.这个进程有可能是僵尸进程 D.这个进程当前状态是停止状态 6. 以下描述正确的有 [多选] A.程序运行过程中产生异常会产生信号 B.通过终端键盘CtlrC可以产生一个SIGQUIT信号 C.通过系统调用接口raise只能产生一个SIGABRT信号 D.通过alarm接口可以在n秒钟之后产生一个SIGALRM信号 7. 以下描述正确的有 A.只能使用signal函数自定义信号捕捉函数 B.若当前进程处于阻塞状态则此时到来的信号暂时无法处理。 C.使用signal函数进行信号捕捉函数修改时可以指定SIG_DFL设置为忽略处理 D.使用signal函数进行信号捕捉函数修改时可以指定SIG_IGN设置为忽略处理 8. 以下描述正确的有 A.所有的未决信号都会立即被处理 B.自定义处理方式的信号会返回用户态执行信号捕捉函数 C.所有的信号处理方式都是在用户态完成信号捕捉的 D.自定义处理方式的信号会在执行完信号捕捉函数后直接返回用户态主控流程 9. 下列选项中会导致用户进程从用户态切换到内核态的操作是。 I 整数除以零   II sin函数调用   III read 系统调用 A.仅 I、 II B.仅 I、 III C.仅 II、 III D.I、 II 和 III 10. 两个线程并发执行以下代码假设a是全局变量初始为1那么以下输出______是可能的[多选]  void foo(){aa1;printf(%d ,a);} A.3 2 B.2 3 C.3 3 D.2 2 11. 以下描述正确的有 [多选] A.函数可重入指的是函数中可以在不同的执行流中调用函数会出现数据二义问题 B.函数不可重入指的是函数中可以在不同的执行流中调用函数会出现数据二义问题 C.函数不可重入指的是函数中可以在不同的执行流中调用函数而不会出现数据二义问题 D.函数可重入指的是函数中可以在不同的执行流中调用函数而不会出现数据二义问题 12. 以下描述正确的有 A.在一个函数中若对局部变量进行了操作则这个函数一定是不可重入的 B.在一个函数中若对全局变量进行了操作则这个函数一定是可重入的 C.在一个函数中若对全局变量进行了原子操作则这个函数一定是可重入的 D.在一个函数中若对局部变量进行了原子操作则这个函数一定是不可重入的 答案及解析 1. AC SIGKILL and SIGSTOP不能被捕捉阻塞忽略这里9号信号就是管理员信号就是防止你把所有信号都设定自定义动作导致进程不能退出的情况 2. AC A正确B错误 因为kill -l 选项才是查看信号种类的选项方式 C正确CtrlC会向当前终端的前台进程发送SIGINT信号中断正在运行在前台的程序 D错误 CtrlZ会使当前终端的前台进程进入停止态而不是退出 3. B 未决信号指的是进程收到了信号被添加到未决信号集合中但是暂时还没有被处理的信号。因此A错误B正确 非可靠信号在进行注册时会查看是否已经有相同信号添加到未决集合中如果有则什么都不做因此非可靠信号只会添加一次因此处理完毕后会直接移除准确来说是先移除后处理。而可靠信号会重复添加信号信息到sigqueue链表中相当于可靠信号可以重复添加处理完毕后因为有可能还有相同的信号信息待处理因此并不会直接移除而是检测没有相同信号信息后才会从pending集合中移除 因此C和D选项错误 4. BD 信号阻塞就是可以继续接受信号但是暂时不处理指定信号实现原理就是在block阻塞信号集合中标记指定的信号被标记的信号收到后暂时不处理 因此A错误B正确 信号忽略只是说信号的处理方式就是忽略处理并不表示不再接收指定信号 因此C错误D正确 5. ABCD A正确 信号被阻塞则暂时不被处理SIGKILL/SIGSTOP除外因为无法被阻塞这里说的是可能性因此不做太多纠结 B正确 自定义处理之后信号的处理方式有可能不再是进程退出 C正确 僵尸进程因为已经退出因此不做任何处理 D正确 进程停止运行则将不再处理信号 6. AD A正确程序运行异常退出也是系统检测到异常后向进程发送指定的异常信号程序对信号做出的处理方式就是退出所导致的 B错误 CtrlC产生SIGINT信号而并非SIGQUIT信号 C错误 raise接口可以向当前调用进程发送任意信号 D正确 alarm函数相当于设置一个定时器在n秒钟后向进程发送SIGALRM信号 7. D A sigaction接口也可以自定义信号处理方式 B 信号会打断进程当前的阻塞状态去处理信号 C SIG_DFL为信号默认处理方式 8. B A错误 未决信号是在从程序运行在内核态返回用户态的时候进行处理 B正确 自定义处理方式会执行用户定义的处理函数因此会返回用户态运行 C错误 只有自定义处理方式的信号会在用户态进行处理 D错误 自定义处理方式的信号会在执行完信号捕捉函数后先返回内核态 9. B 程序运行从用户态切换到内核态的操作中断/异常/系统调用 I 会导致程序异常分母不能为0 II 库函数并不会引起运行态的切换 III 系统调用接口 因此只有 I 和 III 符合条件。 10. ABCD 当函数内的操作非原子时因为竞态条件造成的数据二义 aa1 和 printf(%d, a) 之间有可能会被打断 a初始值为1 当A线程执行完aa1后a是2这时候打印会打印2 线程B执行时1打印3 当A线程执行完aa1后a是2这时候时间片轮转到B线程进行1打印3 然后时间片轮转回来也打印3 这两个是比较显而易见的但是还有一些 特殊情况需要注意 printf函数实际上并不是直接输出而是把数据放入缓冲区中因此有可能A线程将打印的2放入缓冲区中还没来得及输出这时候B线程打印了3时间片轮转回来就会后打印2aa1本身就不是原子操作因此有可能同时进行操作都向寄存器中加载1进去然后进行1后将2放回内存因此有可能会打印2和2 11. BD 函数的重入指的是一个函数在不同执行流中同时进入运行其中不可重入指的是一旦重入就有可能会出问题而可重入就是不管怎么重入都不会有特殊影响 根据对函数可重入与不可重入的理解分析正确选项为 B和D 选项 12. B 函数是否可重入的关键在于函数内部是否对全局数据进行了不受保护的非原子操作其中原子操作指的是一次完成中间不会被打断的操作表示操作过程是安全的 因此如果一个函数中如果对全局数据进行了原子操作但是因为原子操作本身是不可被打断的因此他是可重入的 A错误函数的重入对局部变量并无影响 B错误全局变量的操作若并非原子操作则有可能会出问题 C正确 D错误局部变量的操作本身就不影响重入所以函数是可重入的 本篇完。 下一部分开始Linux系统的最后一大部分多线程。 下一篇零基础Linux_21(多线程)页表详解轻量级进程pthread_create。