Linux——进程控制

目录

进程创建

fork函数

fork用法

fork失败原因

进程终止

进程常见退出方法

进程等待

进程等待的必要性

wait()

waitpid()

status

进程替换

替换函数

execl

execv

execlp

execvp

execle

execvpe

execve系统调用

命名的理解

简易shell


进程创建

fork函数

        在上一篇中我们已经知道了他是什么,也知道了他怎么用,这里就不过多赘述了,但是我们还是要看一下,fork创建子进程操作系统都做了什么。

  • 分配新的内存块和内核数据结构给子进程。
  • 将父进程部分数据结构内容拷贝至子进程。
  • 添加子进程到系统进程列表当中。
  • fork返回,开始调度器调度。

        我们一步一步看,创建了一个子进程,系统中也就多了一个进程,再次强调:进程 = 内核数据结构 + 代码和数据。为子进程分配和初始化他的数据结构,子进程的数据结构都是来自父进程,进行拷贝操作,再把他添加到运行队列中。

        但是子进程没有从硬盘加载到内存这个操作,所以他没有自己的代码和数据,只能共享父进程的,代码通常也是只读的,共享没有问题,但是数据是可能会修改的,所以数据必须要分离。分离就给你拷贝一份,但是子进程拷贝根本就用不到的数据,那么这个进程创建出来也只是多了个进程,并且和父进程一模一样,要是再不退出那就一直占着空间,那这就是浪费空间

        所以创建子进程不需要将不会被访问或只读的数据进行拷贝,只用拷贝将来会写入的数据,但是将来发生什么是是不知道的啊,所以操作系统选择了写时拷贝来进行父子进程数据的分离,从而保证了进程的独立性。

fork用法

  1. 一个父进程希望复制自己,与子进程同时执行不同的代码。
  2. 一个进程要执行不同的程序,例如子进程fork返回后调用exec函数,这后面再说。

fork失败原因

  1. 系统中有太多进程。
  2. 实际用户的进程数超过了限制。

进程终止

进程终止时操作系统要释放进程申请的相关内核数据结构和代码。

进程退出的场景:

  • 代码跑完,结果正确
  • 代码跑完,结果不正确
  • 代码没有跑完,程序崩溃

这些退出场景是什么意思,接下来就来看一下。

        通常我们写的main函数的返回值不都是0吗,这个0是什么,是啥意思呢?

        其实这里并不一定要是0,它表示的是这个进程的退出码。0代表代码跑完了,结果是正确的。在命令行中,我们想要获取最近一次进程的退出码使用的是:echo $?。

        通常情况下,0表示success结果正确,而非0表示结果不正确,非0值有无数个,不同的值表示不同的错误,从而给程序运行结束之后定位错误原因。

#include <stdio.h>
#include <unistd.h>
#include <string.h>int main()
{for (int i = 0; i < 135; i++){printf("[%d]: %s\n", i, strerror(i));}return 0;
}

 这里为什么写i < 135是因为strerror打印的信息一共就135条。

这只是strerror提供的错误信息,如果不想用它的也可以自己定义退出码。

 

ls也是一个程序,如果使用错误的选项或者不存在的文件会怎么样呢?

错误码是2,也可以对应上面的图找找,2也就是No such file or directory。


这里演示一下程序崩溃。

int main()
{printf("hello 1\n");printf("hello 1\n");printf("hello 1\n");int* p = NULL;*p = 1; // 野指针错误printf("hello 2\n");printf("hello 2\n");printf("hello 2\n");return 0;
}

这为啥是139啊?所以程序崩溃的时候,退出码无意义。

进程常见退出方法

正常的终止方法:

  1. 只有main函数内的return语句就是终止进程的。
  2. 调用exit();他在任何地方调用都表示终止进程。
  3. _exit();

他们两个就什么区别呢?

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main()
{printf("hello");sleep(3);exit(1);
}

如果我们不写"\n"代表数据还在缓冲区内,exit会帮我们刷新缓冲区。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main()
{printf("hello");sleep(3);_exit(1);
}

_exit是一个系统调用,它直接帮我们终止程序。

 

        那我们就要再来说一下系统调用和库函数的关系了,库函数是用户为了更好的使用而封装了系统调用接口,exit也是把_exit封装了一下,\n会自动刷新缓冲区,exit也会刷新,所以这个缓冲区一定不在操作系统中,如果在的话,_exit也会刷新缓冲区,所以这个缓冲区是C标准库维护的,关于这些细节以后会说的。


进程等待

进程等待的必要性

  1. 如果子进程退出,父进程如果不读取子进程的退出信息,子进程就会变成僵尸进程,这样就会造成内存泄漏。
  2. 子进程一旦变成僵尸进程,就算是kill -9 命令也无法将其杀死,因为无法杀死一个已经死去的进程。
  3. 对于一个子进程,父进程必须要知道自己派给子进程的任务完成有没有完成。
  4. 父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息。

还是这段代码,还是这个现象。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{pid_t id = fork();if (id < 0){perror("fork");exit(1); // 进程运行完毕,结果不正确}else if (id == 0){// 子进程int cnt = 3;while (cnt--){printf("cnt = %d, I am child, pid: %d, ppid: %d\n", cnt, getpid(), getppid());sleep(1);}exit(0);}else{// 父进程while (1){printf("I am parent, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);}}return 0;
}

        三秒后子进程退出,父进程还在运行,父进程没有读取子进程的退出信息,所以子进程进入了僵尸状态,不想让他这样那就让父进程接受它的退出信息就可以了。

wait()

这里又是一个系统调用接口。

作用:等待任意子进程

参数:是输出型参数,获取子进程的退出状态,不需要知道退出状态可设置为NULL。

返回值:等待成功返回等待进程的pid,失败返回-1。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if (id < 0){perror("fork");exit(1); // 进程运行完毕,结果不正确}else if (id == 0){// 子进程int cnt = 3;while (cnt--){printf("cnt = %d, I am child, pid: %d, ppid: %d\n", cnt, getpid(), getppid());sleep(1);}exit(0);}else{// 父进程printf("I am parent, pid: %d, ppid: %d\n", getpid(), getppid());pid_t ret = wait(NULL); // 阻塞式的等待if (ret > 0){printf("等待子进程成功,ret: %d\n", ret);}while (1){printf("I am parent, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);}}return 0;
}

        子进程先运行三秒,使用wait的时候是阻塞式的等待,只有等待成功才往后执行,父进程也回收了处于僵尸状态的子进程。

waitpid()

还有一个就是waitpid,pid_t waitpid(pid_t pid, int* status, int options);

作用:等待指定子进程或任意子进程。

参数:

1.如果pid=-1,表示等待任意一个子进程。

2.如果pid>0,等待进程id与pid相等的子进程。

3.status如果想要结果需要传入status的地址,用来获取子进程的退出结果。

4.options默认为0,表示阻塞等待。

返回值:

1.正常返回的是子进程的pid。

2.如果选型中是WNOHANG,而子进程没有退出,返回的就是0.

3.调用出错返回-1

waitpid(pid, NULL, 0) == wait(NULL)

status

下面就来看看status是怎么用的。

int main()
{pid_t id = fork();if (id < 0){perror("fork");exit(1); // 进程运行完毕,结果不正确}else if (id == 0){// 子进程int cnt = 3;while (cnt--){printf("cnt = %d, I am child, pid: %d, ppid: %d\n", cnt, getpid(), getppid());sleep(1);}exit(5);}else{// 父进程printf("I am parent, pid: %d, ppid: %d\n", getpid(), getppid());int status = 0;pid_t ret = waitpid(id, &status, 0); // 阻塞式的等待if (ret > 0){printf("等待子进程成功,ret: %d, status: %d\n", ret, status);} }return 0;
}

        还是子进程运行3秒,然后退出,此时父进程等待,等待成功拿到了退出信息,status应该帮我们拿到exit(5)的值,但是这里为什么不是5呢?

        这就要说一下,status不是按照整型来使用的,而是按照bit位的方式,将32个bit位进行划分,现在就先看低16位,这16位的前8位就存放着退出信息,如何拿到这8位,先向右移8位再按位与上0xFF,这样只有后八位是1,其余都是0就可以拿到这八位。

// ...
int status = 0;
pid_t ret = waitpid(id, &status, 0); // 阻塞式的等待
if (ret > 0)
{printf("等待子进程成功,ret: %d, status8位退出码: %d\n", ret, status >> 8 & 0xFF); // 0xFF -> 0000...0000 1111 1111
} 
// ...

这样就拿到了5。

        还要再说的一点就是,平常写代码的时候遇到程序崩溃,准确的来说应该是进程崩溃,这是由操作系统发送信号才让他崩溃的,那么这种信号被存放在status的低7位

//...
int status = 0;
pid_t ret = waitpid(id, &status, 0); // 阻塞式的等待
if (ret > 0)
{printf("等待子进程成功,ret: %d, status7位信号: %d, status8位退出码: %d\n", ret, status & 0x7F, status >> 8 & 0xFF);// 0x7F -> 0000...0000 0111 1111 ; 0xFF -> 0000...0000 1111 1111
}
//...

信号为0就代表正常退出。使用kill -l来查看所有的信号。

如果这时候子进程来一个除0错误会怎么样呢?

        8号信号就可以知道是SIGFPE,这是浮点数错误。这就代表程序崩溃,收到操作系统发来的信号,此时的退出码就没有意义了。

        如果子进程是个死循环,父进程使用kill -9 信号处理。

所以程序异常不止是内部的问题,也有可能是外力操作。

        既然我们已经知道了怎么拿到这几位信息,但这样是不是太麻烦了,所以系统为我们提供了两个宏:

  • WIFEXITED(status):用于查看进程是否是正常退出,本质是检查是否收到信号。(Wait IF EXIT EnD,方便记忆)
  • WEXITSTATUS(status):用于获取进程的退出码(Wait EXIT STATUS)。
int main()
{pid_t id = fork();if (id < 0){perror("fork");exit(1); // 进程运行完毕,结果不正确}else if (id == 0){// 子进程int cnt = 3;while (cnt--){printf("cnt = %d, I am child, pid: %d, ppid: %d\n", cnt, getpid(), getppid());sleep(1);}exit(5);}else{// 父进程printf("I am parent, pid: %d, ppid: %d\n", getpid(), getppid());int status = 0;pid_t result = waitpid(id, &status, 0); // 阻塞式的等待if (result > 0){if (WIFEXITED(status)){// 子进程正常退出printf("子进程执行完毕,子进程的退出码: %d\n", WEXITSTATUS(status));}else{printf("子进程异常退出: %d\n", WIFEXITED(status)); }}}return 0;
}

        waitpid的第三个参数option,默认0的情况下就是阻塞等待,库中也帮我们定义了一个宏define WNOHANG 1;,它的值其实就是1,宏就是为了帮助我们见名知意,Wait NO HANG,hang这个单词也有挂起吊死的意思,有的时候一个进程怎么都不动,CPU可能很忙,没有调度这个进程,所以他要么在阻塞队列中要么在等待被调度,所以NO HANG就是非阻塞等待的意思。

        那么waitpid是怎么做到阻塞或者非阻塞的呢,在waitpid这个函数中,操作系统要先检测子进程是否退出,在task_struct中存放着这些信息。

        如果子进程退出了,如果有status就把status的信息填充好,然后返回它的pid。

        如果子进程没退出,还要再检测你的option是几,是0就是阻塞等待,在CPU中的寄存器中保存它的上下文,然后把父进程挂起,放到等待队列中,所以进程阻塞是阻塞在函数内部的这一行,后面的代码就不执行了,只有满足条件的时候才被唤醒,从寄存器中拿到这一行的位置,继续向后执行;如果是1就代表非阻塞,函数直接就return,继续执行其他代码。

int main()
{pid_t id = fork();if (id < 0){perror("fork");exit(1); // 进程运行完毕,结果不正确}else if (id == 0){// 子进程int cnt = 3;while (cnt--){printf("cnt = %d, I am child, pid: %d, ppid: %d\n", cnt, getpid(), getppid());sleep(1);}exit(5);}else{// 父进程int quit = 0;while (!quit){int status = 0;pid_t res = waitpid(id, &status, WNOHANG); // 非阻塞式的等待if (res > 0){// 等待成功,子进程退出printf("等待子进程退出成功,退出码: %d\n", WEXITSTATUS(status));sleep(2);quit = 1;}else if (res == 0){printf("子进程还在运行,父进程可以继续处理其他事\n");sleep(1);}else {// 等待失败}}}return 0;
}

        我们前面老是提到,进程具有独立性,但是进程的退出码也是子进程的数据,父进程为什么能拿到?

        子进程退出了,变成了僵尸进程,但也保留着进程PCB的信息,在PCB中也会有退出码和退出信号等信息,这就要说到wait和waitpid了,他们两个是系统调用接口,说白了就是系统帮我们用这两个函数从子进程的PCB中拿到了status的值


进程替换

        在进程创建的部分说到了fork创建子进程,之后父子各自执行父进程代码的一部分,如果子进程不想执行父进程的代码,换言之就是让子进程执行一个新的代码,这就需要进程替换来完成,进程替换是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载到调用进程的地址空间中。

        子进程要调用一种exec函数,从而执行另一个程序。当进程调用这种exec函数时,该进程的用户空间代码和数据完全被新程序替换页表的映射关系重新建立,从新程序的第一行开始执行。调用exec并不会创建新的进程,它的本质就是加载程序的函数,所以调用exec前后该进程的pid并未改变

替换函数

返回值:只有替换失败了才有返回值为-1,替换成功会把自己也替换掉,所以成功没有返回值。

替换函数有六种以exec开头的函数,它们统称为exec函数,先来看第一个:

execl

int execl(const char *path, const char *arg, ...);

        第一个参数是要执行程序的路径,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。

        例如ls -l -a,ls就是/usr/bin下的一个可执行程序,所以就用代码调用一下它。

int main()
{printf("begin---------------------\n");execl("/usr/bin/ls", "ls", "-l", NULL);printf("end-----------------------\n");return 0;
}

这样就在代码中使用了命令,奇怪的一点是,我的end代码怎么没有打印出来?

        execl是程序替换,调用函数成功,会将当前进程的代码和数据都进行替换,只要替换成功,后面的代码就不会执行了。

        在进程替换之前,父子进程的代码是共享的数据写时拷贝。进程替换后,子进程要写入新的程序并重新建立映射关系,所以父子进程的代码也要分离代码也要进行写时拷贝,这样父子进程的代码和数据都分离了。

execv

        再来看下一个函数:

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

        从命名上execl最后一个是“l”,可以理解为list也就是列表,最后一个参数也是可变参数列表,把参数一个一个写出来。而execv最后一个是“v”,这就是vector,像一个数组一样,最后一个参数也是指针数组。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>#define NUM 16int main()
{pid_t id = fork();if (id == 0){// 子进程printf("子进程开始运行,pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);char* const _argv[NUM] = {"ls","-l","-a",NULL};execv("/usr/bin/ls", _argv);exit(1);}else {// 父进程printf("父进程开始运行,pid: %d, ppid: %d\n", getpid(), getppid());int status = 0;pid_t res = waitpid(id, &status, 0); // 阻塞等待,子进程先运行,父进程再运行if (res > 0){printf("wait success, exit_code: %d\n", WEXITSTATUS(status));}}return 0;
}

和execl的使用没有太大区别。

execlp

再来看下一个函数:

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

        execlp中的“l”代表列表的形式传参,而“p”代表的是传入一个文件名,他自己会在环境变量中找。

execlp("ls", "ls", "-l", "-a", NULL);

execvp

那么execvp也就好理解了。

int execvp(const char *file, char *const argv[]);
char* const _argv[NUM] = {"ls","-l","-a",NULL
};
execvp("ls", _argv);

execle

下一个是execle。

int execle(const char *path, const char *arg, ..., char *const envp[]);

        函数名没有“p”,所以不会从环境变量中找,“l”是列表的形式,“e”就代表第三个参数你可以设置环境变量。

char* const _env[NUM] = {"MY_VAL=100",NULL
};
execle("...", "...", NULL, _env); 

假如我不行使用ls这种系统提供的程序,我想要替换自己写的程序,那就需要传入这个程序的路径、可变参数,最后一个就可以传入想给这个进程的环境变量。

execvpe

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

        这个函数也就不太难理解了,传入的参数可以在环境变量中找到,以数组的形式传入,可以传入环境变量给替换的进程。

execve系统调用

上面所说的6个函数都是对这个系统调用的封装,为了满足不同的需求。

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

命名的理解

  • l(list) : 表示参数用列表形式
  • v(vector) : 参数用数组形式
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量


简易shell

        简易的shell就是一个命令行解释器,原理就是当有命令需要执行的时候,shell(父进程)创建子进程让它执行命令,而shell(父进程)只需要等待子进程退出。

通过这个函数拿到从键盘中输入的字符串。

通过这个函数分割字符串。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#define NUM 1024
#define SIZE 32
#define SEP " "// 保存用户输入的字符串
char cmd_line[NUM];// 保存分割后的字符数组
char* g_argv[SIZE];int main()
{// 0. 命令行解释器:一定是一个不退出的进程while (1){// 1. 打印提示信息printf("[root@localhost myshell]# "); // 没有\n是不会打印的fflush(stdout); // 刷新缓冲区memset(cmd_line, '\0', sizeof(cmd_line));// 2. 获取输入的指令和选项if (fgets(cmd_line, sizeof(cmd_line), stdin) == NULL) // 判断{continue;}cmd_line[strlen(cmd_line) - 1] ='\0' ; // 输入的应该是:ls -l -a\n,要把\n去掉// 3. 命令行字符串解析g_argv[0] = strtok(cmd_line, SEP); // 第一次要传入字符串int index = 1;while (g_argv[index++] = strtok(NULL, SEP)); // 第二次如果还要解析上一个字符串就要传入NULL// 创建子进程pid_t id = fork();if (id == 0){// 子进程execvp(g_argv[0], g_argv);exit(1);}else {// 父进程int status = 0;pid_t ret = waitpid(id, &status, 0); // 阻塞等待if (ret > 0) printf("exit_code: %d\n", WEXITSTATUS(status));}}
}

这样一个简易的shell就完成了,但是这个shell还有一点缺点。

        当我们运行自己写shell,输入cd ..命令返回上一级目录的时候就会有问题。

使用了cd命令,但是没有执行,这时为什么呢?

        因为这行命令都交给了子进程,子进程执行cd只会影响它的当前路径,所以需要特殊处理,说白了它就是父进程中的函数调用。

        原来我们讲过export添加环境变量也是要在父进程中添加的从而影响全局。这也是父进程中的函数调用,要注意的是添加环境变量的时候定义了字符数组,并把要添加的环境变量拷贝到数组中,使用数组添加环境变量,

还可以继续优化一下,ls可以加上配色,输入ll的也可以处理一下。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#define NUM 1024
#define SIZE 32
#define SEP " "// 保存用户输入的字符串
char cmd_line[NUM];// 保存分割后的字符数组
char* g_argv[SIZE];// 存放想要添加的环境变量
char g_val[32];int main()
{// 0. 命令行解释器:一定是一个不退出的进程while (1){// 1. 打印提示信息printf("[root@localhost myshell]# "); // 没有\n是不会打印的fflush(stdout); // 刷新缓冲区memset(cmd_line, '\0', sizeof(cmd_line));// 2. 获取输入的指令和选项if (fgets(cmd_line, sizeof(cmd_line), stdin) == NULL) // 判断{continue;}cmd_line[strlen(cmd_line) - 1] ='\0' ; // 输入的应该是:ls -l -a\n,要把\n去掉// 3. 命令行字符串解析g_argv[0] = strtok(cmd_line, SEP); // 第一次要传入字符串int index = 1;if (strcmp(g_argv[0], "ls") == 0){g_argv[index++] = "--color=auto"; // 也可以为ls加上配色}if (strcmp(g_argv[0], "ll") == 0){g_argv[0] = "ls";g_argv[index++] = "-l";g_argv[index++] = "--color=auto";}while (g_argv[index++] = strtok(NULL, SEP)); // 第二次如果还要解析上一个字符串就要传入NULL// export添加环境变量也是要在父进程添加的if (strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL){strcpy(g_val, g_argv[1]); // 如果使用g_val[1]去添加环境变量会添加失败int ret = putenv(g_val);if (ret == 0) printf("export success\n");continue;}// 4. 处理内置命令,是让父进程(shell)执行的,是要影响父进程的if (strcmp(g_argv[0], "cd") == 0){if (g_argv[1] != NULL)chdir(g_argv[1]);continue;}// 5. 创建子进程pid_t id = fork();if (id == 0){// 子进程execvp(g_argv[0], g_argv);exit(1);}else {// 父进程int status = 0;pid_t ret = waitpid(id, &status, 0); // 阻塞等待if (ret > 0) printf("exit_code: %d\n", WEXITSTATUS(status));}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/268105.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[计算机网络]--I/O多路转接之poll和epoll

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、poll函…

通过修改host文件来访问GitHub

前言&#xff1a; 由于国内环境的原因&#xff0c;导致我们无法流畅的访问GitHub&#xff0c;。 但是我们可以采取修改host文件来实现流畅访问。 缺点&#xff1a;需要不定时的刷新修改。 操作流程 一、查询IP地址 以下地址可以查询ip地址 http://ip.tool.chinaz.com/ htt…

VisualStudio2022安装教程

1.下载安装 最近要帮机器视觉的同学介绍C#&#xff0c;所以把安装过程梳理一下给大家&#xff1a; VisualStudio 是微软公司的强大的集成开发环境&#xff0c;这里不多说&#xff0c;感兴趣可以上网搜下 学习的话&#xff0c;下载免费的社区版就好 下载地址&#xff1a;https:…

k8s二进制部署的搭建

1.1 常见k8s安装部署方式 ●Minikube Minikube是一个工具&#xff0c;可以在本地快速运行一个单节点微型K8S&#xff0c;仅用于学习、预览K8S的一些特性使用。 部署地址&#xff1a;Install Tools | Kubernetes ●Kubeadm Kubeadm也是一个工具&#xff0c;提供kubeadm init…

【JS】解构赋值注意点,解构赋值报错

报错代码 const 小明 { email: 6, pwd: 66 } const 小刚 { email: 9, pwd: 99 }const { email } 小明 const { email } 小刚 报错图 原因 2个常量重复&#xff0c;重复在同一个作用域内是不能重复的&#xff0c;例如大括号内{const a 1; const a 2} 小伙伴A提问 问&…

2023年第十四届蓝桥杯大赛软件类省赛C/C++大学A组真题

2023年第十四届蓝桥杯大赛软件类省赛C/C大学A组部分真题和题解分享 文章目录 蓝桥杯2023年第十四届省赛真题-平方差思路题解 蓝桥杯2023年第十四届省赛真题-更小的数思路题解 蓝桥杯2023年第十四届省赛真题-颜色平衡树思路题解 蓝桥杯2023年第十四届省赛真题-买瓜思路题解 蓝桥…

springcloud和基础服务的搭建以及封装

代码仓库地址&#xff1a;https://github.com/zhaoyiwen-wuxian/springcloud-common page分页也进行了封装&#xff0c;只需要添加到pom中&#xff0c;将会自动进行分页&#xff0c;并且后端不需要写任何的分页数据。只需要前端自己传分页参数即可&#xff0c;并且里面封装了很…

Neoverse S3 系统 IP:机密计算和多芯片基础设施 SoC 的基础

第三代Neoverse系统IP Neoverse S3 产品推出了我们的第三代基础设施特定系统 IP&#xff0c;这是下一代基础设施 SOC 的理想基础&#xff0c;适用于从 HPC 和机器学习到 Edge 和 DPU 的各种应用。S3 机箱专注于为我们的合作伙伴提供 Chiplet、机密计算等关键创新以及 UCIe、DD…

2024.03.01作业

1. 基于UDP的TFTP文件传输 #include "test.h"#define SER_IP "192.168.1.104" #define SER_PORT 69 #define IP "192.168.191.128" #define PORT 9999enum mode {TFTP_READ 1,TFTP_WRITE 2,TFTP_DATA 3,TFTP_ACK 4,TFTP_ERR 5 };void get_…

每日一题——LeetCode1572.矩阵对角线元素的和

方法一 遍历矩阵 如果矩阵中某个位置&#xff08;x,y&#xff09;处于对角线上&#xff0c;那么这个位置必定满足&#xff1a; xy 或 xy len-1 &#xff08;len为矩阵长度&#xff09; var diagonalSum function(mat) {let len mat.length;let sum 0;for (let i 0; i …

智慧运维是什么,智能建筑设施运维管理系统怎么样

推进数字化转型。数字化转型是中小企业向专业化、信息化发展的必由之路。通过引进先进的信息技术和管理系统&#xff0c;公司应加大对数字技术的投入&#xff0c;提高生产流程、成本和效率&#xff0c;提高企业的竞争力。 操作界面的简单易用性 综合页面设计简单明了&#xff…

构建安全的REST API:OAuth2和JWT实践

引言 大家好&#xff0c;我是小黑&#xff0c;小黑在这里跟咱们聊聊&#xff0c;为什么REST API这么重要&#xff0c;同时&#xff0c;为何OAuth2和JWT在构建安全的REST API中扮演着不可或缺的角色。 想象一下&#xff0c;咱们每天都在使用的社交媒体、在线购物、银行服务等等…

Stable Diffusion 模型分享:AAM XL (Anime Mix)(动漫截屏风格 XL)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 AAM XL (Anime Mix) 是一个动漫截屏风格的模型&#xff0c;是 AAM - AnyLoRA Anime Mix 模…

手撕Java集合之简易版Deque(LinkedList)

在目前&#xff0c;许多互联网公司的面试已经要求能手撕集合源码&#xff0c;集合源码本身算是源码里比较简单的一部分&#xff0c;但是要在面试极短的10来分钟内快速写出一个简易版的源码还是比较麻烦的&#xff0c;很容易出现各种小问题。所以在平时就要注重这方面的联系。 以…

第二十一周周报

文献阅读&#xff1a;Recent Advances of Monocular 2D and 3D Human Pose Estimation: A Deep Learning Perspective 摘要&#xff1a;在本文中&#xff0c;作者提供了一个全面的 2d到3d视角来解决单目人体姿态估计的问题。首先&#xff0c;全面总结了人体的二维和三维表征。…

【JavaEE进阶】CSS选择器的常见用法

CSS选择器的主要功能就是选中页面指定的标签元素&#xff0c;选中了元素&#xff0c;才可以设置元素的属性。 CSS选择器主要有以下几种: 标签选择器类选择器id选择器复合选择器通配符选择器 接下来用代码来学习这几个选择器的使用。 <!DOCTYPE html> <html lang&q…

047 内部类

成员内部类用法 /*** 成员内部类** author Admin*/ public class OuterClass {public void say(){System.out.println("这是类的方法");}class InnerClass{public void say(){System.out.println("这是成员内部类的方法");}}public static void main(Stri…

TDengine 在 DISTRIBUTECH 分享输配电数据管理实践

2 月 27-29 日&#xff0c;2024 美国国际输配电电网及公共事业展&#xff08;DISTRIBUTECH International 2024&#xff09;在美国-佛罗里达州-奥兰多国家会展中心举办。作为全球领先的年度输配电行业盛会&#xff0c;也是美洲地区首屈一指的专业展览会&#xff0c;该展会的举办…

基于springboot+vue的智能学习平台系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

蓝桥杯备战刷题three(自用)

1.合法日期 #include <iostream> #include <map> #include <string> using namespace std; int main() {map<string,int>mp;int days[13]{0,31,28,31,30,31,30,31,31,30,31,30,31};for(int i1;i<12;i){for(int j1;j<days[i];j){string sto_strin…