2024年最全Linux进程控制_linux系统的进程控制,C C++面试题2024笔试

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

exit函数

exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

  1. 执行用户通过atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit

image-20220819175143613

对比执行:

代码:

image-20220819180530147

执行结果:

image-20220819180456678

代码:image-20220819180638458

执行结果:

image-20220819180609819

进程等待

进程等待必要性

  • 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法 杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

进程等待的方法

wait方法

image-20220819184157600

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
	成功返回被等待进程pid,失败返回-1。
参数:
	输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

使用举例:

注意:检测进程运行状态的脚本:

while :; do ps ajx | grep proc | grep -v grep; sleep 1; echo "--------------------------------"; done

代码:

image-20220819215317719

运行截图:

刚开始运行:

image-20220819215047534

使用kill命令杀死子进程后,但是父进程并未受到子进程的返回的PID,所以变成了僵尸状态的进程:

image-20220819215126504

当父进程等待子进程后,子进程被回收:

image-20220819215238911

waitpid方法(系统调用)

image-20220819220717803

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
	当正常返回的时候waitpid返回收集到的子进程的进程ID;
	如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
	如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
	pid:
		Pid=-1,等待任一个子进程。与wait等效。
		Pid>0.等待其进程ID与pid相等的子进程。
	status(输出型参数:通过调用该函数,从函数内部拿出特定的):
		WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
		WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
	options:
		WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。阻塞等待是0(等待的一方处于等待状态,注意不是被等待的一方)。

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息(从子进程的task_struct中获得退出码)。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。
获取子进程status
  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特 位):

image-20220820150058754

0~7:表示处理异常情况(代码跑完和代码异常退出的两种情况)

8~15:保存退出码(代码跑完,结果对还是代码跑完结果不对这两种情况)

终止信号(低7位):进程退出时受到的信号。进程退出,如果异常退出,是因为这个进程受到了特定的信号。

注意:使用kill -l命令可以查看所有终止信号

image-20220820154921050

注意:没有0号信号。

使用举例:

代码:

image-20220820151438085

执行结果:

image-20220820151500409

代码:

执行结果:

程序刚开始执行:

image-20220820155301620

执行kill -9 子进程PID后:

image-20220820155416874

问:是否可以通过一个全局变量来保存子进程的退出码然后父进程访问呢?

答:不可以,因为进程具有独立性,且子进程的数据具有写时拷贝的特性。

问:我们先看退出码还是退出信号?

答:先看退出信号,确定程序是否正常结束,如果正常结束,再看退出码,查看程序出现的问题;如果异常终止,就没必要再看退出码了,此时的退出码毫无意义,即使退出码此时为0。

区分阻塞等待和非阻塞等待

image-20220924163406704

阻塞等待

当我们调用某些函数的时候,因为条件不就绪,需要我们阻塞等待,本质:就是当前进程自己变成阻塞状态等条件(可能是任意的软硬件)就绪的时候。再被唤醒。

举例:之前的都是阻塞等待,此处不再举例。

非阻塞等待

当我们调用某些函数的时候,条件并没有就绪,此时调用函数立即返回,并且此时可以继续做一些其它的任务,同时可以采用轮询的方式对条件是否满足进行询问,当条件满足的时候调用函数。

  1 #include<stdio.h>                                                                              
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<sys/types.h>
  5 #include<sys/wait.h>
  6 int main()
  7 {
  8   pid_t id = fork();
  9   if(id == 0)
 10   {
 11     //子进程
 12     while(1)
 13     {
 14       printf("我是子进程,我的pid:%d,我的ppid:%d\n", getpid(), getppid());
 15       sleep(1);
 16     }
 17     exit(1004);
 18   }
 19   else if(id > 0)
 20   {
 21     //父进程
 22     //printf("我是父进程,我的pid:%d,我的ppid:%d\n", getpid(), getppid());
 23     //int status = 0;
 24     //pid\_t ret = waitpid(-1, &status, 0 );
 25     //if(ret >0)
 26     //{
 27     // printf("等待成功!%d, exit sig:%d,exit code:%d\n", ret, status&0x7F, (status>>8)&0xFF);
 28     //}
 29     int status = 0;
 30     while(1)
 31     {
 32       pid_t ret  = waitpid(-1, &status, WNOHANG);//基于非阻塞的轮询等待方案:WNOHANG
 33       if(ret > 0)
 34       { 
 35         printf("等待成功!%d, exit sig:%d,exit code:%d\n", ret, status&0x7F, (status>>8)&0xFF);
 36         break;
 37       }
 38       else if(ret == 0)
 39       {
 40         //等待成功了,但是子进程没有退出
 41         printf("子进程未准备好,父进程做其他事情...\n");
 42         sleep(1);
 43       }
 44       else 
 45       {
 46         //出错了
 47       }
 48     }
 49   }
 50   else 
 51   {
 52     //错误情况
 53   }
 54 
 55   return 0;
 56 }              

运行截图:

image-20220924165247410

进程程序替换

定义和原理(是什么or为什么)

是什么

让创建出来的子进程执行全新的程序。

为什么

我们一般在服务器设计(Linux编程)的时候,往往需要子进程干两种事情。

  1. 让子进程执行父进程的代码片段(服务器代码)
  2. 让子进程执行磁盘中一个全新的程序(shell,想让客户端执行对应的程序,通过我们的进程,执行其他人写的进程代码等等)。比如c/c++调用别人写的java或者python代码程序。

image-20221001125746765

程序替换的原理:

  1. 将磁盘中的程序,加载入内存结构
  2. 重新建立页表映射,谁执行程序替换,就重新建立谁的页表映射(子进程)

效果:让父进程和子进程彻底分离,并让子进程执行一个全新的程序。

注意:上面的这个过程由操作系统来完成,我们调用的是接口。

问:这个过程有没有创建新的进程?

答:没有创建新的进程,因为子进程的内核数据结构根本没变(包括子进程的PID),改变的只是页表的映射关系,仍旧是原来的进程,只是执行的是新的程序。

相关函数及使用(怎么办)

函数总览

使用man execlman execve指令来查看相关的函数

image-20221001130727462

image-20221001130826069

具体函数使用
execl
int execl(const char *path, const char *arg, ...);//l可以理解成list

path指的是路径,arg就是程序如何执行即执行方式,比如我们平时执行的ls -a -l,那么这个参数就是"ls","-a","-l",NULL这就是我们的执行方式。注意:最后一个必须是NULL,标识如何执行程序的参数传递完毕。

注意:path路径的写法无论是绝对路径还是相对路径都是可以的。

使用举例:

image-20221001134022584

运行截图:

image-20221001134058993

问:为什么后面的printf语句没有执行?

答:因为一旦替换成功,是将当前进程的所有代码和数据全部都替换了,而printf就是代码,替换后代码就没了,所以后面的printf语句没有执行。

问:execl这个程序替换函数是否需要判断返回值?

答:不需要。例如:ret = execl(.....),程序一旦替换成功了,就不会有返回值返回到原进程中,自然也不会被接收,那么这个返回值是否还有意义呢?答案是有意义的,但是只有程序替换失败了才会有返回值返回到原进程中,才是才会被接收,并且执行ret = execl(......)语句 后面的代码。这个返回值最多只能得到什么原因导致的替换失败。所以不需要判断返回值,替换成功了执行替换后的代码,替换失败了执行ret = execl(......)代码后面的代码。

用子进程来实现进程替换:

代码:

image-20221001143320051

运行截图:

image-20221001143407117

结论:子进程进行进程替换,并不会影响父进程(进程具有独立性)。

问:在进程替换的时候是如何做到父进程和子进程独立的?

答:数据层面发生写时拷贝,当进行程序替换的时候,我们可以理解成为代码和数据都发生了写时拷贝,

execv
int execv(const char *path, char *const argv[]);//v可以理解为vector

argv是指针数组类型,数组中的元素的类型是char*。

image-20221001144702707

代码:

image-20221001150846947

运行截图:

image-20221001150907833

execlp
int execlp(const char *file, const char *arg, ...);//p表示的是PATH
//file:我们想要执行的程序,命名带p的,可以不带路径(注意,只有在PATH路径下的才可以,自己写的不可以),只说出执行哪一个程序即可

代码:

  1 #include<stdio.h>  
  2 #include<unistd.h>  
  3 #include<sys/wait.h>  
  4 #include<stdlib.h>  
  5 int main()  
  6 {  
  7   printf("我是一个进程,我的pid:%d\n", getpid());  
  8   //ls -a -l 
  9   //execl("/usr/bin/ls","ls","-a", "-l", NULL); 
 10   //printf("我是一个进程,我执行完毕了,我的pid:%d\n", getpid()); 
 11   pid\_t id = fork();  
 12   if(id == 0)  
 13   {  
 14     //子进程 
 15     //子进程执行全新的程序 
 16     printf("我是子进程,我的pid:%d\n", getpid());  
 17     //execl("/usr/bin/ls","ls", "-a", "-l", NULL); 
 18     //char \*const argv[] = {(char\*)"ls", (char\*)"-a", (char\*)"-l",(char\*)NULL}; 
 19     //execv("/usr/bin/ls", argv); 
 20     execlp("ls","ls", "-a", "-l", NULL);
 21     exit(-1);//只要exit执行了,就意味着execl程序替换失败了
 22   }
 23   //父进程
 24   int status = 0;
 25   int ret = waitpid(id, &status, 0);
 26   if(ret == id)
 27   {
 28     printf("父进程等待成功!\n");
 29     sleep(2);
 30   }
 31   return 0;
 32 } 

执行结果:

image-20221001153608666

execvp
int execvp(const char \*file, char \*const argv[]);

代码练习:

image-20221001162649025

execle
int execle(const char \*path, const char \*arg, ...,char \*const envp[]);//envp就是环境变量
//注意:添加环境变量给目标进程是覆盖式的,如果手动传了环境变量,原来默认的环境变量就不存在了
//问:如何手动传系统自带的PATH环境变量?
//答:extern char\*\*environ;然后将environ传过去即可

代码练习:

myexec.c文件

  1 #include<stdio.h>                                                                                         2 #include<unistd.h>
  3 #include<sys/wait.h>
  4 #include<stdlib.h>
  5 int main()
  6 {
  7   printf("我是一个进程,我的pid:%d\n", getpid());
  8   //ls -a -l
  9   //execl("/usr/bin/ls","ls","-a", "-l", NULL);
 10   //printf("我是一个进程,我执行完毕了,我的pid:%d\n", getpid());
 11   pid_t id = fork();
 12   if(id == 0)
 13   {
 14     //子进程
 15     //子进程执行全新的程序
 16     printf("我是子进程,我的pid:%d\n", getpid());
 17     char\* const _env[] = {(char\*)"MYTH=Hello, this is MYPATH!", NULL};
 18     //execl("/usr/bin/ls","ls", "-a", "-l", NULL);
 19     //char \*const argv[] = {(char\*)"ls", (char\*)"-a", (char\*)"-l",(char\*)NULL};
 20     //execv("/usr/bin/ls", argv);
 21     //execvp("ls",argv);
 22     //execl("/home/ljg/linux\_for\_practice/2022\_10\_1/mycmd", "mycmd",NULL);
 23     execle("./mycmd", "mycmd", NULL,_env);
 24     exit(-1);//只要exit执行了,就意味着execl程序替换失败了
 25   }
 26   //父进程
 27   int status = 0;
 28   int ret = waitpid(id, &status, 0);
 29   if(ret == id)
 30   {
 31     printf("父进程等待成功!\n");
 32     sleep(2);                       
 33   }            
 34   return 0;
 35 }  

makefile文件:

image-20221001190523672

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

8 int ret = waitpid(id, &status, 0);
29 if(ret == id)
30 {
31 printf(“父进程等待成功!\n”);
32 sleep(2);
33 }
34 return 0;
35 }


makefile文件:


![image-20221001190523672](https://img-blog.csdnimg.cn/img_convert/5d5ef3674467c79a1e97e57e6fea65f9.png)




[外链图片转存中...(img-cFXtJLe3-1715604482516)]
[外链图片转存中...(img-cUifTeOa-1715604482516)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值