既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
exit函数
exit最后也会调用exit, 但在调用exit之前,还做了其他工作:
- 执行用户通过atexit或on_exit定义的清理函数。
- 关闭所有打开的流,所有的缓存数据均被写入
- 调用_exit
对比执行:
代码:
执行结果:
代码:
执行结果:
进程等待
进程等待必要性
- 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
- 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法 杀死一个已经死去的进程。
- 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
进程等待的方法
wait方法
#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
代码:
运行截图:
刚开始运行:
使用kill
命令杀死子进程后,但是父进程并未受到子进程的返回的PID,所以变成了僵尸状态的进程:
当父进程等待子进程后,子进程被回收:
waitpid方法(系统调用)
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比特 位):
0~7:表示处理异常情况(代码跑完和代码异常退出的两种情况)
8~15:保存退出码(代码跑完,结果对还是代码跑完结果不对这两种情况)
终止信号(低7位):进程退出时受到的信号。进程退出,如果异常退出,是因为这个进程受到了特定的信号。
注意:使用
kill -l
命令可以查看所有终止信号:
注意:没有0号信号。
使用举例:
代码:
执行结果:
代码:
执行结果:
程序刚开始执行:
执行kill -9 子进程PID
后:
问:是否可以通过一个全局变量来保存子进程的退出码然后父进程访问呢?
答:不可以,因为进程具有独立性,且子进程的数据具有写时拷贝的特性。
问:我们先看退出码还是退出信号?
答:先看退出信号,确定程序是否正常结束,如果正常结束,再看退出码,查看程序出现的问题;如果异常终止,就没必要再看退出码了,此时的退出码毫无意义,即使退出码此时为0。
区分阻塞等待和非阻塞等待
阻塞等待
当我们调用某些函数的时候,因为条件不就绪,需要我们阻塞等待,本质:就是当前进程自己变成阻塞状态等条件(可能是任意的软硬件)就绪的时候。再被唤醒。
举例:之前的都是阻塞等待,此处不再举例。
非阻塞等待
当我们调用某些函数的时候,条件并没有就绪,此时调用函数立即返回,并且此时可以继续做一些其它的任务,同时可以采用轮询的方式对条件是否满足进行询问,当条件满足的时候调用函数。
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 }
运行截图:
进程程序替换
定义和原理(是什么or为什么)
是什么
让创建出来的子进程执行全新的程序。
为什么
我们一般在服务器设计(Linux编程)的时候,往往需要子进程干两种事情。
- 让子进程执行父进程的代码片段(服务器代码)
- 让子进程执行磁盘中一个全新的程序(shell,想让客户端执行对应的程序,通过我们的进程,执行其他人写的进程代码等等)。比如c/c++调用别人写的java或者python代码程序。
程序替换的原理:
- 将磁盘中的程序,加载入内存结构
- 重新建立页表映射,谁执行程序替换,就重新建立谁的页表映射(子进程)
效果:让父进程和子进程彻底分离,并让子进程执行一个全新的程序。
注意:上面的这个过程由操作系统来完成,我们调用的是接口。
问:这个过程有没有创建新的进程?
答:没有创建新的进程,因为子进程的内核数据结构根本没变(包括子进程的PID),改变的只是页表的映射关系,仍旧是原来的进程,只是执行的是新的程序。
相关函数及使用(怎么办)
函数总览
使用man execl
和man execve
指令来查看相关的函数
具体函数使用
execl
int execl(const char *path, const char *arg, ...);//l可以理解成list
path指的是路径,arg就是程序如何执行即执行方式,比如我们平时执行的ls -a -l
,那么这个参数就是"ls","-a","-l",NULL
这就是我们的执行方式。注意:最后一个必须是NULL,标识如何执行程序的参数传递完毕。
注意:path路径的写法无论是绝对路径还是相对路径都是可以的。
使用举例:
运行截图:
问:为什么后面的printf语句没有执行?
答:因为一旦替换成功,是将当前进程的所有代码和数据全部都替换了,而printf就是代码,替换后代码就没了,所以后面的printf语句没有执行。
问:execl这个程序替换函数是否需要判断返回值?
答:不需要。例如:
ret = execl(.....)
,程序一旦替换成功了,就不会有返回值返回到原进程中,自然也不会被接收,那么这个返回值是否还有意义呢?答案是有意义的,但是只有程序替换失败了才会有返回值返回到原进程中,才是才会被接收,并且执行ret = execl(......)
语句 后面的代码。这个返回值最多只能得到什么原因导致的替换失败。所以不需要判断返回值,替换成功了执行替换后的代码,替换失败了执行ret = execl(......)
代码后面的代码。
用子进程来实现进程替换:
代码:
运行截图:
结论:子进程进行进程替换,并不会影响父进程(进程具有独立性)。
问:在进程替换的时候是如何做到父进程和子进程独立的?
答:数据层面发生写时拷贝,当进行程序替换的时候,我们可以理解成为代码和数据都发生了写时拷贝,
execv
int execv(const char *path, char *const argv[]);//v可以理解为vector
argv是指针数组类型,数组中的元素的类型是char*。
代码:
运行截图:
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 }
执行结果:
execvp
int execvp(const char \*file, char \*const argv[]);
代码练习:
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文件:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**