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

Linux-进程控制

目录

一、进程创建

1.1、fork()函数

 1.2、fork的返回值

1.3、写实拷贝(Copy-on-Write,COW)

1.4、fork常规用法

1.5、fork调用失败的原因

二、进程退出

三、进程等待

1、wait和waitpid

1.1、解决僵尸进程问题

1.2、status参数

程序正常运行情况:

程序异常情况:

1.3、option参数

四、进程程序替换

1、快速认识进程程序替换

2、程序替换的接口

execl及其引出的相关概念: 

子进程替换之后,代码也发生写实拷贝

替换可以替换我们自己写的程序

程序替换不会创建新的进程

其他接口:

五、自定义shell的编写

1、打印命令行提示符

2、获取命令行字符串

3、命令行解析

4、执行

5、处理内建命令

6、初始化环境变量表

7、完整代码


一、进程创建

1.1、fork()函数

在linux中fork函数是⾮常重要的函数,它从已存在进程中创建⼀个新进程。新进程为⼦进程,⽽原进程为⽗进程。
# include <unistd.h>
pid_t fork ( void );
返回值:子进程中返回 0 ,父进程返回⼦进程 id ,出错返回 -1
进程调⽤fork,当控制转移到内核中的fork代码后,内核做:
  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

分配新的内核数据结构即pcb以及mm_struct、vm_area_struct

当⼀个进程调⽤fork之后,就有两个⼆进制代码相同的进程。⽽且它们都运⾏到相同的地⽅。但每个进程都将可以开始它们⾃⼰的旅程,看如下程序。
  1 #include<stdio.h>2 #include<unistd.h>3 #include<stdlib.h>4 int main()5 {6   pid_t pid;7   printf("Before: pid is %d\n",getpid());8   if((pid=fork())==-1)9   {10     perror("fork()");11     exit(1);12   }13   printf("After: pid is %d ,fork() return %d\n",getpid(),pid);                                                                                           14   sleep(1);15   return 0;16 }

fork()创建子进程,给父进程返回子进程pid,给子进程自己返回0;那么3493就是父进程的pid,子进程pid就是3494

所以,fork之前父进程独立执行,fork()之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定

 1.2、fork的返回值

⼦进程返回0,
⽗进程返回的是⼦进程的pid。

1.3、写实拷贝(Copy-on-Write,COW)

通常,⽗⼦代码共享,⽗⼦再不写⼊时,数据也是共享的,当任意⼀⽅试图写⼊,便以写时拷⻉的⽅式各⾃⼀份副本。具体⻅下图:

原理:写时复制是一种延迟拷贝技术,多个进程在最初可以共享同一块内存区域,只有当其中某个进程尝试对这块共享内存进行写入操作时,系统才会为该进程创建一个独立的内存副本,让其在副本上进行写入,而其他进程继续使用原来的内存,以此避免不必要的内存拷贝,提高系统性能。 实现方式Linux 通过页表机制来实现写时复制。当创建新进程时,内核会为新进程创建虚拟地址空间,并将父进程的页表复制一份给子进程。此时,父子进程的页表项都指向相同的物理内存页面,并将这些页面标记为只读。当某个进程试图写入共享页面时,CPU 会产生一个写保护错误,内核捕捉到这个错误后,会为该进程分配新的物理内存页面,将原来共享页面的内容复制到新页面中,然后更新页表项,使其指向新的页面,同时将新页面设置为可写,而其他进程的页表项仍指向原来的共享页面,不受影响。

优点 节省内存:在进程创建初期,多个进程共享相同的内存页面,无需为每个进程分配独立的内存空间来复制数据,只有在进程实际需要写入时才会分配新的内存,大大节省了内存资源,尤其在有大量进程同时运行且需要共享大量数据的情况下,能有效降低系统的内存压力。 提高进程创建速度:由于不需要在进程创建时立即进行大量的数据拷贝,只是复制页表等少量信息,使得进程创建的速度大幅提高。这对于需要频繁创建进程的应用场景,如服务器端的多进程并发处理等,能够显著提升系统的响应能力和整体性能。 减少数据不一致性:写时复制保证了在多个进程共享数据期间,只要没有进程进行写入操作,它们所访问的数据始终是一致的。只有当某个进程进行写入时,才会创建独立的副本,避免了对共享数据的意外修改,从而减少了数据不一致性的风险,提高了系统的稳定性和可靠性。 优化系统资源利用:写时复制机制使得系统能够更合理地分配内存资源,将有限的内存资源优先分配给真正需要写入数据的进程,避免了内存的浪费。同时,由于减少了内存拷贝操作,也降低了 CPU 的负担,使得系统资源能够得到更充分、有效的利用,提高了整个系统的运行效率。

1.4、fork常规用法

⼀个⽗进程希望复制⾃⼰,使⽗⼦进程同时执⾏不同的代码段。例如,⽗进程等待客⼾端请求,
⽣成⼦进程来处理请求。
⼀个进程要执⾏⼀个不同的程序。例如⼦进程从fork返回后,调⽤exec函数。

1.5、fork调用失败的原因

系统中有太多的进程
实际⽤⼾的进程数超过了限制

二、进程退出

程序运行最终的三种情况:

  1. 程序运行完毕,结果正确
  2. 程序运行完毕,结果不正确
  3. 程序异常终止

而最终收到一个程序最终运行结果的是其父进程

从main函数入手:

main函数结束,return返回的是一个退出码,为0代表正常结束结果正确,非0表示结果不正确,并且这些非0的退出码都表示一个错误信息;程序异常终止也会返回一个退出码,但是这个退出码一定不是我们想要收到的退出码,也就是说程序异常终止时,返回一个无意义的退出码

其他函数的返回值只是一个单纯的值,不是退出码,只有main函数的return才是退出码

  1 #include<stdio.h>2 3 int main()4 {5   FILE * fp = fopen("hellp.exe","r");                                                                                                                    6   if(fp == NULL)7   {8     return 1;9   }10   fclose(fp);11   return 0;12 }

echo &?打印最近一个进程的退出码,proc读文件失败返回1那么退出码为1,之后再打印,是打印的echo的退出码,为0

退出码写在这个进程的task_struct内部

退出码是给操作系统看的,想要查看每一个退出码的信息,用strerror(头文件<string.h>,总共134个退出码)

可以直接返回errno(头文件errno.h),就是这个退出码;也可以将errno放在strerror里面打印出错误码的错误信息

调用exit(头文件stdlib.h)也可以退出进程,任何地方调用exit()都是结束进程,返回给父进程退出码

exit(status),这个status就是错误码

调用_exit也能终止进程并且返回错误码

这两个有区别:exit是c语言库中的函数,_exit是系统调用接口层面的调用,库函数是系统调用的包装,也就是说exit调用的是_exit,但是exit退出时会刷新缓冲区,_exit不会刷新

看到这个例子:

首先是带\n表示,\n能够刷新缓冲区

此时exit、_exit所在代码的结果都一样;但是若是去掉\n,不让\n刷新缓冲区

exit还是会刷新缓冲器,最后打印main!

来看_exit:

此时没有打印main!,说明缓冲区没有被刷新,程序直接结束了

这就是两者的区别

exit最后也会调⽤_exit, 但在调⽤_exit之前,还做了其他⼯作:
1. 执⾏⽤⼾通过 atexit或on_exit定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写⼊
3. 调⽤_exit
这也可以说明一个问题,就是缓冲区在哪里?首先exit是库函数,而_exit是系统层面,那么说明缓冲区一定不在操作系统中,而是在库缓冲区,也就是C语言提供的缓冲区

三、进程等待

进程等待必要性:

1、子进程退出,父进程没有收到退出信息,子进程变为僵尸进程,这样的进程kill -9也无法杀死,因为不能杀掉一个已经死去的进程,那么父进程没有等待就会出现资源泄漏

2、父进程通过等待获取子进程任务完成如何

3、父进程通过等待获取子进程的退出信息从而来回收子进程的资源(这一点最重要)

1、wait和waitpid

1.1、解决僵尸进程问题

先来查看一个僵尸子进程,并且如何通过等待的方式来解决僵尸进程

子进程运行之后退出但是父进程不等待回收

子进程退出之后,成为僵尸进程Z

父进程等待wait解决僵尸进程

wait等待任意一个子进程结束,类型为pid_t,收到的是僵尸进程的pid;

status获取子进程退出信息,若是不关系则可以设置成NULL。

还有一点:当子进程没有退出时,因为fork分流,此时父进程也在执行,此时wait时没有子进程退出,那么父进程就处于阻塞状态,等待子进程的退出

waitpid:第二个参数和wait一样,第一个参数指定要等待的子进程pid,若是指定为-1,那么就是等待任意一个子进程pid,此时和wait一样

若是指定的pid不存在,那么等待失败,此时返回no child process错误码,表示没有这个子进程;并且此时waitpid返回值为-1

若是等待的pid不存在:

给id加1,则此id一定不存在

1.2、status参数

这是一个输出型参数,由操作系统填充

这个参数表示的是子进程的退出信息

程序正常运行情况:

子进程exit(1)引入话题:

子进程的退出码设置为1,status是子进程的退出信息,但是父进程打印的时候却不是1

那么status是怎么获得退出信息的?

status是一个整形遍历,32个比特位,高16为为0,次低8位表示正常运行时的退出信息

那么当exitcode为1的时候,status就是2^8

验证是不是这样,让父进程打印时打印(status>>8)&0XFF:

将子进程退出码改为10,再来验证

bash中echo $?获取退出码也是这样实现的:

那么定义一个exitcode全局变量,子进程退出时返回这个变量可以达到和status相同的效果吗?不行,这是因为这个全局变量在子进程中修改时会发生写实拷贝,由于进程的独立性,父进程是拿不到这个全局变量exitcode的;只能通过操作系统的系统调用接口拿到,就比如这里的waitpid();

程序异常情况:

那么当程序不是正常运行时,也就是进程退出的第三种情况,出现异常时status是怎么获取的?

当程序没有异常时,status的最低的7个比特位为0;一旦程序出现异常,最低的7个比特位表示的就是异常的退出信号;并且此时的退出码无意义。

异常信号有哪些?kill -l查看

验证异常:

子进程一直执行,在子进程运行时kill -9杀死进程,出现异常,父进程通过status收到退出信号,一定是SIGKILL 9 号信号:

继续验证:在子进程中设置异常代码:

等待具体是怎么做到的?

子进程的PCB中会存放exit_code、exit_signal退出信息

父进程通过操作系统调用系统接口wait或者waitpid获取这些退出信息;

getpid、getppid也是相似的原理;

理解为什么要有僵尸进程:

进程信息保留:子进程结束时,它需要向父进程返回一些信息,如退出状态、资源使用情况等。僵尸进程状态可以暂时保留这些信息,以便父进程通过特定的系统调用(如wait系列函数)来获取。如果没有僵尸进程状态,子进程结束后立即彻底消失,父进程就可能无法获取到这些重要信息,从而难以对其子进程的执行结果进行正确的处理和后续的资源回收 父进程同步:僵尸进程的存在可以让父进程有机会与子进程的结束进行同步。父进程通过等待僵尸子进程的结束,可以确保在子进程完成所有任务并返回相关信息后,再继续执行后续的操作。这种同步机制有助于保证程序的正确性和稳定性,避免父进程在子进程还未完成时就进行一些依赖子进程结果的操作,从而导致错误或不一致的情况。 资源管理信号:僵尸进程对于操作系统来说,也是一种信号,表明有子进程已经结束,需要进行资源回收等后续处理。操作系统可以通过检测僵尸进程的存在,来触发相应的资源回收机制,确保系统资源能够被及时、正确地释放和重新分配。虽然僵尸进程本身也占用一定的系统资源(主要是 PCB 所占用的内存空间),但这是为了实现进程间的正确通信和资源管理所付出的一种暂时的代价。

有关status在系统中的定义的宏: 
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): WIFEXITED非 零,提取子进程退出码。(查看进程的退出码)
WIFEIXTED(status)返回值为1,表示子进程正常终止情况:
让子进程异常终止:

1.3、option参数

option设置为0,表示父进程阻塞等待子进程退出;

设置为宏WNOHANG,表示父进程非阻塞等待子进程退出;非阻塞等待就是只会单次不持续的等待,也就是说父进程等待子进程一次就没有了,就算子进程没有退出也不会处于阻塞的状态等待
pid 指定的⼦进程没有结束,则 waitpid() 函数返回 0 ,不予以等待。若正常结束,则返回该⼦进程的ID,若wiatpid调用失败,则结束等待返回-1(调用失败的情况很少,一般都是我们自己传参出错,比如传的子进程id不存在)

因为是非阻塞等待。所以使用这个option时一般都要套一层循环;

非阻塞等待的优点:相比于阻塞等待,非阻塞等待在等待的同时可以让父进程进行其他任务,因为程序不会阻塞,所以其他的任务也能执行;这样就提高了效率,这个效率提高的不是子进程退出的效率,而是等待子进程退出前,父进程能够并发地完成其他任务;

代码:


四、进程程序替换

1、快速认识进程程序替换

通过一段代码认识程序进程替换:

程序进程替换就是把excel之后的代码程序进行覆盖式替换,替换的内容是excel里面的内容,替换之后之前代码程序就不存在了,执行的是替换之后的程序;

在替换的过程中,没有创建新的进程,只是代码替换;

excel没有成功返回值,只有失败返回值,但是也用不到返回值,因为若是替换之后执行到后面程序了,那么一定失败;

替换的是代码段和数据段的内容

看看替换失败的例子:

2、程序替换的接口

NAME
       execl, execlp, execle, execv, execvp, execvpe - execute a file

SYNOPSIS
       #include <unistd.h>

       extern char **environ;

       int execl(const char *path, const char *arg, ...);
       int execlp(const char *file, const char *arg, ...);
       int execle(const char *path, const char *arg,..., char * const envp[]);
       int execv(const char *path, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[], char *const envp[]);

execl及其引出的相关概念: 

execl,l表示列表的意思,path表示替换程序的路径,后面的...表示多参数,对应execl中的l列表;路径指明要执行谁,多参数指明要怎么执行

命令行怎么传那么execl就怎么写,但是必须要以NULL结尾,表示参数传递完成

通过这个接口来理解更多概念:

子进程替换之后,代码也发生写实拷贝

 将execl放在子进程中观察现象:

观察到子进程发生程序替换之后,父进程在替换之后的程代码还是可以执行;

输出结论:程序替换的时候不仅仅是数据,子进程的代码也会发生写实拷贝

替换可以替换我们自己写的程序

编写一段c++代码,在此时的c程序中的子进程使用程序替换,替换的内容为编写的c++代码,观察运行结果:

还可以替换为python,shell脚本语言,这是因为操作系统具有语言无关性(底层这些语言都被处理为了二进制数据)

程序替换不会创建新的进程

结果显示两个程序的pid一样,都是同一个进程,也就是说,程序替换的时候没有创建新的子进程

其他接口:

int execlp(const char *file, const char *arg, ...);

file表示替换之后进行运行的程序,只需要传文件即可;p表示path,也就是这个接口会使用环境变量PATH来找到指定文件的路径

 int execv(const char *path, char *const argv[]);

path表示要运行的文件所在的路径,argv是一个指针数组,execv中的v表示vector;使用这个接口是,需要传一个指针数组

这个数组也可以写在父进程里面,那么就是父进程传命令行参数给子进程了;这也是main函数的命令行参数的机制,调用相关程序替换的接口来传参;

 int execvp(const char *file, char *const argv[]);和上面一样

 int execvpe(const char *file, char *const argv[], char *const envp[]);

env表示传的环境变量,带e的exec系类的接口传环境变量时是覆盖式的传,也就是看不到从父进程拷贝过来的环境变量了

想要不覆盖式的传:

1、使用不带e的接口,此时不传env,子进程拷贝命令行参数环境变量,这个是放在内核数据结构中的,而子进程创建需要先拷贝父进程部分来创建内核数据结构;

所以不传env时,使用的就是父进程的env即完整的环境变量

若是想要在此基础上新增环境变量,则使用putenv接口:

2、若是想使用带e的接口,那么env传environ,此时子进程的环境变量就是全的,当然也可以使用putenv新增

int execve(const char *filename, char *const argv[], char *const envp[]);只有这个才是系统调用,其他的都是语言封装


五、自定义shell的编写

1、打印命令行提示符

2、获取命令行字符串

在命令行运行一下:

3、命令行解析

通过strtok将命令行字符串按照空格分开

g_argc--才能表示分割后命令的个数,因为会包含一个NULL

4、执行

测试:

将执行阶段放在函数里面:

获取到目录而不是整个路径:

看下面一个现象:

使用cd命令时无法更改父进程的路径:

这是因为执行时是子进程执行的,而打印命令行提示符时,打印的是父进程从bash那通过环境变量读来的;那么此时cd时改变的是子进程的路径而父进程没有改变;下一次循环子进程还是继承的父进程的路径,所以cd的结果不变

那么如何解释ls能够达到正常效果呢?因为在子进程中继承了环境变量,所以父进程通过程序替换函数接口将ls一类的字符串传进去的时候,这些环境变量能够帮助子进程运行,运行的结果显示在bash命令行

5、处理内建命令

若是cd命令,cd之后查看到的还是bash的路径,不会改变;此时需要处理;

处理方法:在子进程开始执行之前,处理cd命令,首先判断是不是cd,然后使用chdir改变当前父进程的路径,这样子进程也会变成相应的路径;

注意只有cd时,默认回到家目录

命令行解析失败就重新循环,打印命令行提示以及输入命令字符串;内建命令cd返回值为true则也重新循环,因为此时是通过chdir改变的,不需要输出什么。

但是有一个问题,就是命令行提示器的路径没有随着cd改变;这是因为当前工作路径虽然被cd改变了,但是环境变量没有改变,而命令行提示器的路径来自于我们写的GetPwd(也就是通过PWD环境变量来的);在shell中,我们使用cd改变工作路径时,环境变量也会改变;所以这里要改变一下GetPwd的逻辑,不能每次获取的都是环境变量中的PWD,要根据当前路径来获取pwd从而改变命令行提示符显示的路径:

要使用系统调用getcwd获取当前工作路径,给pwd再返回

但是上述操作都没有改变环境变量,此时输入env发现环境变量没有改变;那么也要改一下环境变量:

将cwd当前工作路径格式化为"PWD=%s"再写入一张cwd的环境变量表,然后使用putenv将cwdenv中的"PWD=当前工作路径字符串"导入环境变量表中

内建命令echo的实现

echo打印字符串、$?即上一个进程的退出码、$环境变量名打印出这个环境变量的具体内容

这里要设置一个全局变量lastexit即上一次的退出码:

6、初始化环境变量表

自己写一张环境变量表;环境变量表应该从父shell统一而来

7、完整代码

1 #include<iostream>2 #include<cstdio>3 #include<cstring>4 #include<cstdlib>5 #include<unistd.h>6 #include<sys/types.h>7 #include<sys/wait.h>8 #define MAXARGC  1289 //命令行参数表10 char* g_argv[MAXARGC];11 int g_argc = 0;12 //环境变量表13 char* g_env[100];14 int g_envs = 0;15 //1、命令行提示符打印16 #define FORMAT "[%s@%s %s]# "17 #define COMMAND_SIZE 102418 19 const char* GetHome()20 {21     const char* home = getenv("HOME");22     return home == NULL ? "" : home;23 }24 const char* GetUsername()25 {26     const char* username = getenv("USER");                                                                                                             27     return username == NULL ? "None" : username;28 }29 const char* GetHostname()30 {31     const char* hostname = getenv("HOSTNAME");
32     return hostname == NULL ? "None" : hostname;33 }34 char cwd[1024];35 char cwdenv[1024];36 const char* GetPwd()37 {38     //const char* pwd = getenv("PWD");39     const char* pwd = getcwd(cwd , sizeof(cwd));40     if(pwd != NULL)41     {42         snprintf(cwdenv , sizeof(cwdenv) , "PWD=%s" , cwd);43         putenv(cwdenv);44     }45     return pwd == NULL ? "None" : pwd;46 }47 std::string GetDir(const char*  pwd)48 {49     std :: string dir = pwd;50     if(dir == "/") return "/";51     size_t pos = dir.rfind("/");52     if(pos == std::string::npos) return "BUG?";53     return dir.substr(pos+1);54 }55 void MakeCmdPrompt(char* prompt , int size)56 {                                                                                                                                                      57     //将字符串格式化写入prompt中58     snprintf(prompt , size , FORMAT , GetUsername() , GetHostname() ,GetDir(GetPwd()).c_str());59 }
void PrintCmdPrompt()61 {62     char cmd_prompt[COMMAND_SIZE];63     MakeCmdPrompt(cmd_prompt , COMMAND_SIZE);64     printf("%s" , cmd_prompt);65     fflush(stdout);66 }67 //2、获取命令行字符串68 bool GetCommandLine(char* out , int size)69 {70     char* c = fgets(out , size , stdin);71     if(c == NULL) return false;72     out[strlen(out)-1] = 0;//消除回车影响73     if(strlen(out) == 0) return false;74     return true;75 }76 77 //3、命令行解析
W> 78 bool CommandParse(char* commandline , int size)79 {80   #define SEP " "81     g_argc = 0;82     g_argv[g_argc++] = strtok(commandline , SEP);83     while((bool)(g_argv[g_argc++] = strtok(NULL , SEP)));84     g_argc--;                                                                                                                                          85     return g_argc > 0 ? true : false; 86 }
88 void PrintArgv()89 {90     for(int i = 0 ; i < g_argc ; i++)91     {92       printf("argv[%d] -> %s\n" , i , g_argv[i]);93     }94 }95 //4、处理内建命令96 int lastexit = 0;97 bool Dealbuildin()98 {99     std::string cwd = g_argv[0];100     if(cwd == "cd")101     {102         if(g_argc == 1)103         {104             //回到家目录105             std::string home = GetHome(); 106             if(home == "") return true;107             chdir(home.c_str());108         }109         else 110         {111           std::string where = g_argv[1];112           chdir(where.c_str());113         }                                                                                                                                              114         return true;115     }116     else if(cwd == "echo")117     {118         std::string cont = g_argv[1];
119         if(cont == "$?")120         {121             //获取上一次进程退出码,因为执行程序阶段已经记录,所以直接获得122             std::cout<<lastexit<<std::endl;123             lastexit = 0;//每次打印完就置为0124             return true;125         }126         if(cont[0] == '$')127         {128             //获取环境变量129             std::string str = cont.substr(1);//以string形式获取$之后的字符串 130             const char* e = getenv(str.c_str());131             std::cout<<e<<std::endl;132             return true;133         }134         else  135         {136             std::cout<<cont<<std::endl;137             return true;138         }139     }140     return false;141   }142 //5、执行程序143 void execute()                                                                                                                                         144 {145     pid_t id = fork();146     if(id == 0)147     {148       execvp(g_argv[0] , g_argv);
149     }150     int status = 0;151     pid_t rid = waitpid(id , &status , 0);152     if(rid > 0)153     {154         lastexit = WEXITSTATUS(status);155     }156 }157 //6、初始化环境变量表158 void InitEnv()159 {160     extern char** environ;161     g_envs = 0;162     memset(g_env , 0 , sizeof(g_env));163 164     //环境变量本来要从配置文件中来165     //获取环境变量166     for(int i = 0; environ[i]; i++)167     {168         g_env[i] = (char*)malloc(strlen(environ[i]) + 1);169         strcpy(g_env[i] , environ[i]);170         g_envs++;171     }172     //测试是不是我们初始化的表173     g_env[g_envs++] = (char*)"FOR_TEST";                                                                                                               174     g_env[g_envs] = NULL;175 176     //导成环境变量177     for(int i = 0; i < g_envs; i++)178     {putenv(g_env[i]);180     }181     environ = g_env;182 }183 int main()184 {185   InitEnv();186   while(1)187   {188     //1、打印命令行提示符189     PrintCmdPrompt();190     //2、获取命令行字符串191    char commandline[COMMAND_SIZE];192     if(!GetCommandLine(commandline , COMMAND_SIZE))193       continue;194     //printf("%s\n" , commandline);195     //3、命令行解析196     if(!CommandParse(commandline , COMMAND_SIZE))197         continue;198    // PrintArgv();// 4、处理内建命令200    if( Dealbuildin() )201       continue;202    // 5、执行阶段203     execute();204   }205   return 0;206 }

相关文章:

Linux-进程控制

目录 一、进程创建 1.1、fork()函数 1.2、fork的返回值 1.3、写实拷贝&#xff08;Copy-on-Write&#xff0c;COW&#xff09; 1.4、fork常规用法 1.5、fork调用失败的原因 二、进程退出 三、进程等待 1、wait和waitpid 1.1、解决僵尸进程问题 1.2、status参数 程序正…...

【优选算法 | 滑动窗口】滑动窗口算法:高效处理子数组和子串问题

算法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;双指针 在本篇文章中&#xff0c;我们将深入剖析滑动窗口算法的核心原理。从基础概念到实战应用&#xff0c;带你了解如何利用滑动窗口高效解决连续子数组和子串等问题。无论你是算法入门的新手&#xff0c;还是…...

RabbitMQ全栈实践手册:从零搭建消息中间件到SpringAMQP高阶玩法

目录 前言 认识MQ 同步调用 异步调用 技术选型 安装 SpringAMQP 交换机类型 队列交换机绑定 环境搭建 Fanout交换机 声明队列和交换机 消息发送 消息接收 总结 Direct交换机 声明队列和交换机 消息发送 消息接收 总结 Topic交换机 声明队列和交换机 消息…...

头歌实训之存储过程、函数与触发器

&#x1f31f; 各位看官好&#xff0c;我是maomi_9526&#xff01; &#x1f30d; 种一棵树最好是十年前&#xff0c;其次是现在&#xff01; &#x1f680; 今天来学习C语言的相关知识。 &#x1f44d; 如果觉得这篇文章有帮助&#xff0c;欢迎您一键三连&#xff0c;分享给更…...

系统架构设计中的DSSA方法:理论、实践与行业深度应用

引言 在软件架构设计领域&#xff0c;‌DSSA&#xff08;Domain-Specific Software Architecture&#xff0c;领域特定软件架构&#xff09;‌是一种专注于垂直行业或业务领域的架构设计方法论。与通用架构设计不同&#xff0c;DSSA通过提炼领域共性需求、构建可复用资产库&am…...

设计心得——数据结构的意义

一、数据结构 在老一些的程序员中&#xff0c;可能都听说过&#xff0c;程序其实就是数据结构算法这种说法。它是由尼克劳斯维特在其著作《算法数据结构程序》中提出的&#xff0c;然后在一段时期内这种说法非常流行。这里不谈论其是否正确&#xff0c;只是通过这种提法&#…...

【C】初阶数据结构12 -- 冒泡排序

本篇文章主要讲解经典排序算法 -- 冒泡排序。 目录 1 算法思想 2 代码 3 时间复杂度与空间复杂度分析 1&#xff09; 时间复杂度 2&#xff09; 空间复杂度 1 算法思想 选择排序是一种经典的交换排序算法。其算法思想也比较简单&#xff0c;主要是比较相邻元素&…...

HTTP, AMQP, MQTT之间的区别和联系是什么?华为云如何适配?

目录 &#x1f517; 一、共同点&#xff08;联系&#xff09;&#xff1a; &#x1f50d; 二、区别对比&#xff1a; &#x1f4d8; 三、简要说明 1. HTTP 2. AMQP 3. MQTT &#x1f517; 四、三者联系&#xff08;在华为云IoT平台中的应用&#xff09; &#x1f3af; …...

WPF之项目创建

文章目录 引言先决条件创建 WPF 项目步骤理解项目结构XAML 与 C# 代码隐藏第一个 "Hello, WPF!" 示例构建和运行应用程序总结相关学习资源 引言 Windows Presentation Foundation (WPF) 是 Microsoft 用于构建具有丰富用户界面的 Windows 桌面应用程序的现代框架。它…...

CrewAI Community Version(二)——Agent

目录 1. Agent总览2. Agent属性3. 创建Agent3.1 YAML配置3.2 直接用代码定义3.3 运行结果 参考 1. Agent总览 在CrewAI框架中&#xff0c;Agent是一个能具备下列能力的自主单元&#xff1a;   1. 执行特定的任务   2. 基于它的角色和目标进行决策   3. 使用工具完成任务 …...

阿里云VS AWS中国区:ICP备案全攻略与常见误区解析

导语 在中国大陆开展互联网服务时,ICP备案是必不可少的合规步骤。然而,随着云服务的多样化,许多企业在选择备案路径时常常感到困惑。本文将深入解析阿里云和AWS中国区的备案区别,为您提供清晰的操作指南,助您避开备案陷阱,确保业务合规运营。 一、备案基本原则 1. 服务器决定…...

基于libdxfrw库读取样条曲线并离散为点

在计算机辅助设计&#xff08;CAD&#xff09;与制造&#xff08;CAM&#xff09;领域&#xff0c;DXF&#xff08;Drawing Exchange Format&#xff09;格式文件被广泛用于存储与交换矢量图形信息。样条曲线作为DXF文件中常见的复杂曲线类型&#xff0c;其准确读取与离散化处理…...

学习 Apache Kafka

学习 Apache Kafka 是一个很好的选择&#xff0c;尤其是在实时数据流处理和大数据领域。以下是一个系统化的学习建议&#xff0c;帮助你从入门到进阶掌握 Kafka&#xff1a; 1. 先决条件 在开始 Kafka 之前&#xff0c;确保你具备以下基础&#xff1a; Java 基础&#xff1a;K…...

5.3/Q1,GBD数据库最新文章解读

文章题目&#xff1a;The burden and trend prediction of ischemic heart disease associated with lead exposure: Insights from the Global Burden of Disease study 2021 DOI&#xff1a;10.1186/s12940-025-01155-w 中文标题&#xff1a;与铅暴露相关的缺血性心脏病的负担…...

java智慧城管综合管理系统源码,前端框架:vue+element;后端框架:springboot;移动端:uniapp开发,技术前沿,可扩展性强

智慧城管综合执法系统采用B/S模式设计与手机等移动终端架构&#xff0c;采用 java编程语言前端框架&#xff1a;vueelement&#xff1b;后端框架&#xff1a;springboot&#xff1b;数据库&#xff1a;mysql5.7&#xff1b;移动端&#xff1a;uniapp技术开发设计。具有使用与维…...

【锂电池剩余寿命预测】GRU门控循环单元锂电池剩余寿命预测(Matlab完整源码)

目录 效果一览程序获取程序内容代码分享研究内容GRU门控循环单元在锂电池剩余寿命预测中的应用摘要关键词1. 引言1.1 研究背景1.2 研究现状与问题1.3 研究目的与意义2. 文献综述2.1 锂电池剩余寿命预测传统方法2.2 深度学习在锂电池寿命预测中的应用2.3 研究空白与本文切入点3.…...

开发首个Spring Boot应用

&#x1f4cb; 前置条件 &#x1f3af; 在开始之前&#xff0c;请打开终端并运行以下命令以确保已安装正确版本的 Java&#xff1a; $ java -version openjdk version "17.0.4.1" 2022-08-12 LTS OpenJDK Runtime Environment (build 17.0.4.11-LTS) OpenJDK 64-Bi…...

2025第十六届蓝桥杯大赛(软件赛)网络安全赛 Writeup

2025第十六届蓝桥杯大赛&#xff08;软件赛&#xff09;网络安全赛 Writeup 2025第十六届蓝桥杯大赛&#xff08;软件赛&#xff09;网络安全赛 Writeup情报收集黑客密室逃脱 数据分析ezEvtxflowzip 密码破解EnigmaECBTraineasy_AES 逆向分析ShadowPhases 漏洞挖掘分析RuneBrea…...

HTTP 协议深度解析:从基础到实战的完整指南

HTTP&#xff08;HyperText Transfer Protocol&#xff09;是 ​应用层协议&#xff0c;用于客户端&#xff08;浏览器、APP&#xff09;与服务器之间的数据交互。以下从协议原理、核心机制到实际案例全面解析&#xff0c;涵盖 HTTP/1.1 到 HTTP/3 的演进。 一、HTTP 核心特性 …...

5G助力智慧城市的崛起——从概念到落地的技术实践

5G助力智慧城市的崛起——从概念到落地的技术实践 引言&#xff1a;智慧城市中的“隐形脉络” 随着城市化的快速推进&#xff0c;传统的城市管理方式已经难以满足人口增长和资源优化的需求。智慧城市的概念应运而生&#xff0c;通过技术创新实现智能化、可持续发展的城市生态…...

4.25test

R7-5 小黄与研究生会(20) 分数 12 全屏浏览 切换布局 作者 王秀 单位 福州大学 福州大学研究生院怡山的同学们为了在国家对抗新冠疫情期间献出自己的一份力量,他们决定为奋战在一线的医护人员送去了演出。小黄作为研究生协会的会长,他让每位男同学均带去了若干只猫或狗…...

Unity-Shader详解-其一

今天我们来介绍Unity的一大核心组件&#xff1a;shader。 Shader Shader就是我们的着色器&#xff0c;用于控制图形的渲染的计算和生成。 对于不同的引擎&#xff0c;具体实现渲染的方法也不一样&#xff0c;也就是我们俗称的不同的图形引擎API&#xff0c;比如OpenGL,Direct…...

WPF与C++ 动态库交互

WPF与C++动态库交互技术详解 一、基本交互方式概述 WPF应用程序与C++动态库交互主要有以下几种方式: ​​P/Invoke调用​​(平台调用)​​COM互操作​​​​C++/CLI桥接层​​​​内存映射文件​​​​命名管道/Socket通信​​本文将重点介绍最常用的P/Invoke和C++/CLI两种…...

自动化测试实战篇

文章目录 目录1. 自动化实施步骤1.1 编写web测试用例1.2 自动化测试脚本开发1.3 测试报告 目录 自动化实施步骤 1. 自动化实施步骤 1.1 编写web测试用例 注&#xff1a; 因为这里仅作为演示&#xff0c;所以设计的用例并不是非常完整 1.2 自动化测试脚本开发 # common/Util…...

基于pandoc的MarkDown格式与word相互转换小工具开发(pyqt5)

这里写目录标题 开发目标准备工作源代码程序打包其他事项命令行使用pandoc关于pandoc默认表格无边框的说明 开发目标 采用word格式模板&#xff0c;实现高级定制样式。具备配置保存功能&#xff0c;方便快捷。自定义转换选项、pandoc路径。 准备工作 开发环境&#xff1a;Wi…...

JVM知识点(一)---内存管理

一、JVM概念 什么是JVM&#xff1f; 定义&#xff1a; Java Virtual Machine - java程序的运行环境(java二进制字节码的运行环境) 好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收功能数组下标越界越界检查多态 比较jvm jre jdk区别 学习路…...

Apache NetBeans 25 发布

Apache NetBeans 25 已于 2025 年 2 月 20 日发布3。NetBeans 是一个主要面向 Java 的集成开发环境&#xff0c;同时支持 C/C、PHP、JavaScript 和其他编程语言1。以下是一些主要的更新内容&#xff1a; Gradle 的优化与增强&#xff1a;优化单文件测试功能&#xff0c;即使测试…...

【设计模式区别】装饰器模式和适配器模式区别

装饰器模式&#xff08;Decorator Pattern&#xff09;和适配器模式&#xff08;Adapter Pattern&#xff09;都是 结构型设计模式 或者说 包装模式 &#xff08;Wrapper&#xff09;&#xff0c;用于解决对象的组合和扩展问题&#xff0c;但它们的核心目的、结构和使用场景有显…...

矫平机终极指南:特殊材料处理、工艺链协同与全球供应链管理

一、特殊材料矫平&#xff1a;挑战与创新解决方案 1. 高温合金&#xff08;如Inconel 718&#xff09;处理 技术难点&#xff1a; 屈服强度高达1100 MPa&#xff0c;传统矫平力不足 高温下易氧化&#xff0c;需惰性气体保护环境 解决方案&#xff1a; 采用双伺服电机驱动&a…...

stm32进入睡眠模式的几个注意点

&#xff08;1&#xff09;关闭systick &#xff08;2&#xff09;先关闭外设时钟&#xff0c;再屏蔽中断&#xff0c;避免先屏蔽中断再关闭外设时钟导致中断挂起无法进入睡眠模式&#xff08;立即被唤醒&#xff09;。 参考&#xff1a; 注&#xff1a;图片截自《RM0433参考手…...

深入理解网络安全中的加密技术

1 引言 在当今数字化的世界中&#xff0c;网络安全已经成为个人隐私保护、企业数据安全乃至国家安全的重要组成部分。随着网络攻击的复杂性和频率不断增加&#xff0c;保护敏感信息不被未授权访问变得尤为关键。加密技术作为保障信息安全的核心手段&#xff0c;通过将信息转换为…...

学习设计模式《六》——抽象工厂方法模式

一、基础概念 抽象工厂模式的本质是【选择产品簇(系列)的实现】&#xff1b; 抽象工厂模式定义&#xff1a;提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们具体的类&#xff1b; 抽象工厂模式功能&#xff1a;抽象工厂的功能是为一系列相关对象或相互依…...

MySQL 数据类型

文章目录 数据类型数据类型分类数据类型tinyint类型&#xff08;整型&#xff09;总结bit类型&#xff08;字节&#xff09; 浮点类型float类型decimal类型 字符串类型char类型varchar&#xff08;变长字符串&#xff09; char 和 varchar的对比日期类型enum和set类型&#xff…...

基于Tcp协议的应用层协议定制

前言&#xff1a;本文默认读者已掌握 TCP 协议相关网络接口知识&#xff0c;将聚焦于应用层协议的设计与剖析&#xff0c;有关底层通信机制及业务逻辑部分仅作简要概述&#xff0c;不再展开详述。 目录 服务器 一、通信 二、协议 1.序列化与反序列化 2. 封包与解包 三、业…...

Flink反压问题解析

一、什么是反压(Backpressure)? 反压(Backpressure) 是流处理系统中的一种流量控制机制。当下游算子处理速度低于上游数据生产速度时,系统会向上游传递压力信号,迫使上游降低数据发送速率,避免数据堆积和系统崩溃。 Flink 通过动态反压机制实现这一过程,但其副作用是…...

C语言中结构体的字节对齐的应用

一、字节对齐的基本原理 计算机的内存访问通常以固定大小的块&#xff08;如 4 字节、8 字节&#xff09;为单位。若数据的内存地址是块大小的整数倍&#xff0c;称为 自然对齐。例如&#xff1a; int&#xff08;4 字节&#xff09;的地址应为 4 的倍数。 double&#xff08…...

大规模数据同步后数据总条数对不上的系统性解决方案:从字段映射到全链路一致性保障

一、引言 在数据同步&#xff08;如系统重构、分库分表、多源整合&#xff09;场景中&#xff0c;“本地数据一致&#xff0c;生产环境条数对不上”是典型痛点。问题常源于并发处理失控、数据库性能瓶颈、字段映射错误、缓存脏数据等多维度缺陷。本文结合实战经验&#xff0c;…...

美团Java后端二面面经!

场景题是面试的大头&#xff0c;建议好好准备 Q. [美团]如何设计一个外卖订单的并发扣减库存系统&#xff1f; Q.[美团]为啥初始标记和重新标记需要STW&#xff1f; Q.[美团]骑手位置实时更新&#xff0c;如何保证高并发写入&#xff1f; Q.[美团]订单表数据量过大导致查询…...

35-疫苗预约管理系统(微服务)

技术&#xff1a; RuoYi框架 后端: SpringBootMySQLspringCloudnacosRedis 前端: vue3 环境&#xff1a; Idea mysql maven jdk1.8 用户端功能 1.首页:展示疫苗接种须知标语、快速预约模块 2.疫苗列表:展示可接种的疫苗 3.预约接种: 用户可进行疫苗预约接种 修改预约时间 …...

Ext JS模拟后端数据之SimManager

Ext.ux.ajax.SimManager 是 Ext JS 框架中用于拦截 Ajax 请求并返回模拟数据的核心工具,适用于前后端分离开发、原型验证或独立测试场景。它通过配置灵活的规则和模拟处理器(Simlet),帮助开发者在不依赖真实后端的情况下完成前端功能开发。 simlets 是simulated servers的…...

BT169-ASEMI无人机专用功率器件BT169

编辑&#xff1a;ll BT169-ASEMI无人机专用功率器件BT169 型号&#xff1a;BT169 品牌&#xff1a;ASEMI 封装&#xff1a;SOT-23 批号&#xff1a;最新 引脚数量&#xff1a;3 特性&#xff1a;单向可控硅 工作温度&#xff1a;-40℃~150℃ BT169单向可控硅&#xff…...

4月26日星期六今日早报简报微语报早读

4月26日星期六&#xff0c;农历三月廿九&#xff0c;早报#微语早读。 1、广州多条BRT相关线路将停运&#xff0c;全市BRT客运量较高峰时大幅下降&#xff1b; 2、国务院批复&#xff1a;同意在海南全岛等15地设立跨境电商综合试验区&#xff1b; 3、我国首次实现地月距离尺度…...

如何将 sNp 文件导入并绘制到 AEDT (HFSS)

导入 sNp 文件 打开您的项目&#xff0c;右键单击 “Result” 绘制结果 导入后&#xff0c;用户可以选择它进行打印。请参阅下面的示例。要点&#xff1a;确保从 Solution 中选择它。...

Shell脚本-for循环应用案例

在Shell脚本编程中&#xff0c;for循环是一种强大的工具&#xff0c;用于处理重复性任务。无论是批量处理文件、遍历目录内容还是简单的计数任务&#xff0c;for循环都能提供简洁而有效的解决方案。本文将通过几个实际的应用案例来展示如何使用for循环解决具体的编程问题。 案…...

MATLAB基础应用精讲-【基础知识篇】发布和共享 MATLAB 代码

目录 MATLAB发布代码---生成文档pdf 分节符对发布文件的分节 实时脚本 Matlab workspace与m脚本数据共享 发布和共享 MATLAB 代码 在实时编辑器中创建和共享实时脚本 发布 MATLAB 代码文件 (.m) 添加帮助和创建文档 发布 MATLAB 代码文件 (.m) 可创建包括您的代码、注释…...

Shell脚本-while循环语法结构

在Shell脚本编程中&#xff0c;while循环是一种重要的流程控制语句&#xff0c;它允许我们重复执行一段代码&#xff0c;直到指定的条件不再满足为止。与for循环不同&#xff0c;while循环通常用于条件驱动的迭代&#xff0c;而不是基于列表或范围的迭代。本文将详细介绍Shell脚…...

Java基础第四章、面向对象

一、成员变量 示例&#xff1a; 二、JVM内存模型 类变量就是静态变量 三、构造方法 默认构造方法、定义的构造方法(不含参数、含参数) 构造方法重载&#xff1a; this关键字 this关键字应用&#xff1a;对构造方法进行复用&#xff0c;必须放在第一行 四、面向对象的三大特征 1…...

【基础IO上】复习C语言文件接口 | 学习系统文件接口 | 认识文件描述符 | Linux系统下,一切皆文件 | 重定向原理

1.关于文件的预备知识 1.1 文件的宏观理解 广义上理解&#xff0c;键盘、显示器等都是文件&#xff0c;因为我们说过“Linux下&#xff0c;一切皆文件”&#xff0c;当然我们现在对于这句话的理解是片面的&#xff1b;狭义上理解&#xff0c;文件在磁盘上&#xff0c;磁盘是一…...

linux离线部署open-metadata

OpenMetadata 环境及离线资源关闭防火墙禁止防火墙关闭 SELinux 创建用户安装JDK安装mysql安装Elasticsearch安装open-metadata 环境及离线资源 系统&#xff1a;CentOS Linux release 7.9.2009 (Core) JDK&#xff1a;17 Mysql&#xff1a; 8.0 OpenMetadata&#xff1a;1.6.…...

Exposure Adjusted Incidence Rate (EAIR) 暴露调整发病率:精准量化疾病风险

1. 核心概念 1.1 传统发病率的局限性 1.1.1 公式与定义 传统发病率公式为新发病例数除以总人口数乘以观察时间。例如在某社区观察1年,有10例新发病例,总人口1000人,发病率即为10/10001=0.01。 此公式假设所有个体暴露时间和风险相同,但实际中个体差异大,如部分人暴露时间…...