【Linux】第七章 进程控制(进程创建+进程终止+进程等待+进程替换+min_shell)

news2025/1/13 6:03:18

🏆个人主页:企鹅不叫的博客

​ 🌈专栏

  • C语言初阶和进阶
  • C项目
  • Leetcode刷题
  • 初阶数据结构与算法
  • C++初阶和进阶
  • 《深入理解计算机操作系统》
  • 《高质量C/C++编程》
  • Linux

⭐️ 博主码云gitee链接:代码仓库地址

⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!

💙系列文章💙


【Linux】第一章环境搭建和配置

【Linux】第二章常见指令和权限理解

【Linux】第三章Linux环境基础开发工具使用(yum+rzsz+vim+g++和gcc+gdb+make和Makefile+进度条+git)

【Linux】第四章 进程(冯诺依曼体系+操作系统+进程概念+PID和PPID+fork+运行状态和描述+进程优先级)

【Linux】第五章 环境变量(概念补充+作用+命令+main三个参数+environ+getenv())

【Linux】第六章 进程地址空间(程序在内存中存储+虚拟地址+页表+mm_struct+写实拷贝+解释fork返回值)


文章目录

  • 💙系列文章💙
  • 💎一、进程创建
    • 🏆1.fork函数
    • 🏆2.写时拷贝
  • 💎二、进程终止
    • 🏆1.程序退出码
    • 🏆2.程序退出方法
      • 程序退出的几种状态
      • exit()和_exit()
      • slab 分配器
  • 💎三、进程等待
    • 🏆1.程序等待的必要性
    • 🏆2.进程等待方法(重要)
      • wait
      • waitpid
    • 🏆3.status参数
    • 🏆4.阻塞等待和非阻塞等待
      • 阻塞等待
      • 非阻塞等待
  • 💎四、进程替换(重点)
    • 🏆1.替换原理
    • 🏆2.替换函数-记得包含头文件<unistd.h>
      • execl
        • 参数
        • 返回值
      • execv
      • execlp
      • execvp
      • execle和execve
        • 用C程序调用C++程序打印换进变量
        • 导入环境变量
      • 系统接口execve
    • 🏆3.函数命名总结
  • 💎五、做一个shell


💎一、进程创建

🏆1.fork函数

#include <unistd.h>
pid_t fork(void);//pid_t就是int类型
返回值:子进程中返回0,父进程返回子进程id,出错返回-1
  1. 将父进程部分数据拷贝给子进程(写时拷贝):环境变量,当前工作目录
  2. 操作系统会给子进程分配一个新的内存块mm_struct+页表和内核数据结构task_strcut给子进程
  3. 子进程添加到进程列表
  4. fork 返回,开始调度
  5. fork创建子进程就是在内核中通过调用clone实现

fork之前只有父进程单独运行。fork之后父子进程的执行流会分别执行,且相互独立,是父进程先执行还是子进程先执行依赖于调度器的调度,并非一定是父进程先执行。

子进程虽然共享父进程的所有代码,但是它只能从fork之后开始执行,在 CPU 里面有一种寄存器eip程序计数器(又称PC指针)能保存当前的执行进度,通过保存当前正在执行指令的下一条指令,拷贝给子进程,子进程就会从该eip所指向的代码处(即fork之后的代码)开始运行

🏆2.写时拷贝

为什么要写时拷贝?详情看这里 传送门

  • 父进程的代码和数据,子进程不一定全部都会使用。即便使用、也不一定会进行修改
  • 理想状态下,可以把父子进程会修改的内容进行分离,不会修改的部分共享即可。但是这样的实现非常复杂
  • 如果fork的时候,就直接分离父子进程的数据,会增加fork运行的时间复杂度和空间复杂度

只会在需要的时候,拷贝父子需要修改的数据。这样延迟拷贝,变相提高了内存的使用率

💎二、进程终止

🏆1.程序退出码

main函数结束时会return 0,这个0就是程序退出码

查看当前程序的退出码

echo $?
// $? 就是 bash 中最近一次执行完毕时对应的进程退出码

利用以下程序打印一下库函数中strerrror函数内记录的错误码

#include<stdio.h>
#include<string.h>

int main()
{
 int i=0;
 for(i=0;i<134;i++)
 {
     printf("[%d] %s\n",i,strerror(i));
 }
 return 0;
}

以下是部分错误码,发现返回0代表成功,其他都是异常

[Jungle@VM-20-8-centos:~/lesson14_进程控制]$ ./main
[0] Success
[1] Operation not permitted
[2] No such file or directory
[3] No such process
[4] Interrupted system call
[5] Input/output error
[6] No such device or address
[7] Argument list too long
[8] Exec format error
[9] Bad file descriptor
[10] No child processes
[11] Resource temporarily unavailable
[12] Cannot allocate memory
[13] Permission denied
[14] Bad address
[15] Block device required
[16] Device or resource busy
[17] File exists
[18] Invalid cross-device link
[19] No such device
[20] Not a directory
[21] Is a directory
[22] Invalid argument
[23] Too many open files in system
[24] Too many open files
[25] Inappropriate ioctl for device
[26] Text file busy

🏆2.程序退出方法

程序退出的几种状态

  • 代码跑完,结果正确
  • 代码跑完,结果错误
  • 代码没有跑完,提前出现异常终止

错误码表征了程序退出的信息,交由父进程进行读取

exit()和_exit()

一般情况下,我们可以在main函数中return,或者在任何地方使用exit()和_exit()来终止程序,exit 调用是终止进程并且刷新缓冲区关闭文件流,而 _exit 是直接终止进程,不会有任何其他动作,记得包含头文件< unistd.h >< stdlib.h >

image-20221008145700686

slab 分配器

进程=内核结构task/mm_struct等+进程代码、数据,创建对象的过程包括了开辟空间和初始化,对象创建了像 task_struct/mm_struct 这种内核数据结构可能并不会被系统释放,系统就会使用内核的数据结构缓冲池,又称slab分派器,来管理这些仍待使用的内核结构。当有新进程出现的时候,更新内核结构的信息,并将其插入到运行队列中

💎三、进程等待

🏆1.程序等待的必要性

这个概念是根据僵尸进程提出的,为什么进程结束后不直接终止(X 状态)而是要先进入僵尸进程(Z 状态)?

子进程退出后如果父进程不管就会造成所谓的僵尸进程,从而造成内存泄漏,另外一旦变成僵尸进程连 kill 指令也束手无策,因为逻辑上你无法杀死一个已经死去的进程。其次,父进程有义务知道子进程的完成情况且结果是否正确,我们说到的退出码这里也是这样,确认子进程的退出状态就是子进程的退出码需要被父进程所读取。

所以进程等待实际上是父进程回收子进程资源,获取子进程退出信息的重要渠道

🏆2.进程等待方法(重要)

使用到的两个函数和头文件

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int*status);
pid_t waitpid(pid_t pid, int *status, int options);

wait

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
  • 作用:等待子进程退出

  • 返回值:等待成功则返回等待进程的PID,等待失败,返回-1

  • 参数为输出型参数,获取子进程退出状态,不关心则可以设置成为 NULL,status是从子进程的task_struct中拿出来的,子进程会将自己的退出码写入task_struct

    下面是错误写法,正确写法具体看status参数这段

    #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){
            int ret = 5;
            while(ret--){
                printf("child[%d] is running:ret is %d\n", getpid(), ret);
                sleep(1);
            }
            exit(0);
        }
        printf("father wait begin..\n");
        pid_t cur = wait(NULL);
        if(cur > 0){
            printf("father wait:%d success\n", cur);
            //printf("father wait:%d success\n", cur);//直接打印status是错误的!
            //status的低16位才有效,其中这16位的高8位是状态码
        }
        else{
            printf("father wait failed\n");
        }
    }
    
    

    运行程序时,SSH 渠道的 shell 脚本来进行监视功能

    while :; do ps axj | head -1 && ps axj | grep mytest | grep -v grep; sleep 1; echo "**********************"; done
    

    监视窗口大致分为以下四步

    在这里插入图片描述

waitpid

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
  • 返回值:正常返回子进程的pid;如果设置了options,而调用中 waitpid 发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回 -1,这时 errno 会被设置成相应的值以指示错误所在
  • pid:大于0等待其进程ID与pid相等的子进程;等于-1等待任意一个子进程
  • status:同wait,为输出型参数,WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
    WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  • options:若设置为0,则进行阻塞等待,若设置为WNOHANG(非阻塞) 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的PID。

waitpid两种使用方式

pid_t cur = waitpid(id, NULL, 0);//等待指定一个子进程
pid_t cur = waitpid(-1, NULL, 0);//等待任意一个子进程
  pid_t cur = waitpid(-1, NULL, 0);//等待任意一个子进程
    if(cur > 0){
        printf("father wait:%d success\n", cur);
    }
    else{
        printf("father wait failed\n");
    }

🏆3.status参数

包含退出码和退出信号,int* status 是一种输出型参数,其中 status 是由32个比特位构成的一个整数,目前阶段我们只使用低16个位来表示进程退出的结果,正常终止的话 8 比特位保存退出状态,7 比特位保存退出码 0;而如果是被信号 kill 了,他就会大有不同,在未使用空间后面的结尾空间用于保存终止信号,在未使用空间和终止信号之间还有一个比特位大小的core dump 标志

在这里插入图片描述

正确访问状态码的方式,用次低8位表示进程退出时的退出状态,也就是退出码;用低7位表示进程终止时所对应的信号,我们想要拿到这个退出码和信号的值,只要拿到低 16 个比特位中的次低 8 位和低 7 位就可以了

status >> 8 & 0xFF;//次低8位,退出码
status & 0x7F;//低7位,退出信号

以下才是wait那一段的正确用法:

#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){
     int ret = 5;
     while(ret--){
         printf("child[%d] is running:ret is %d\n", getpid(), ret);
         sleep(1);
     }
     exit(10);
 }
 printf("father wait begin..\n");
 int status = 0;
 pid_t cur = waitpid(id, &status, 0);//指定等待上面创建的子进程
 //status的低16位才有效,其中这16位的高8位是状态码
 if(cur > 0){
     printf("father wait:%d success, status exit_code:%d, status exit_signal:%d\n", cur, (status >> 8)& 0xFF, status & 0x7F);
 }
 else
     printf("father wait failed\n");
 }

进程结束看退出码,进程异常看退出信号,如果子进程被kill -9干掉,则退出信号返回9

在这里插入图片描述

关于位运算,库里面提供了以下宏

  • WIFEXITED(status) 查看子进程是否是正常退出的,正常退出为真
  • WIFSIGNALED(status)查看子进程是否为信号终止,信号终止返回真
  • WEXITSTATUS(status) 提取子进程退出码
  • WTERMSIG(status) 提取子进程退出信号
//其余部分代码和上面相同,子进程exit(11)
int status = 0;
pid_t cur = waitpid(id,&status,0);//指定等待上面创建的子进程
if(WIFEXITED(status))//子进程正常退出返回真
{   
    printf("等待成功,子进程pid:%d, 状态:%d,信号:%d\n",cur,WEXITSTATUS(status),WTERMSIG(status));
}
else
{
    printf("非正常退出,子进程pid:%d, 状态:%d,信号:%d\n",cur,WEXITSTATUS(status),WTERMSIG(status));
}

🏆4.阻塞等待和非阻塞等待

waitpid函数中的option参数和阻塞/非阻塞有关

0       阻塞
WNOHANG 非阻塞

阻塞等待

阻塞时,在调用结果返回前,当前线程会被挂起,并在得到结果之后返回

waitpidoption传入0,即为阻塞等待

pid_t id = waitpid(-1,&status,0);//阻塞等待

在子进程被信号干掉或者执行完毕退出之前,父进程不会向后执行代码。在用户层面看来,就是程序被卡住了

非阻塞等待

非阻塞时,如果不能立刻得到结果,则该调用者不会阻塞当前线程,因此对应非阻塞的情况,调用者需要定时轮询查看处理状态。

waitpidoption传入WNOHANG,即为非阻塞等待,期间父进程可以做其他事情,这种多次调用waitpid接口的方式又被称为轮询检测

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int add(int a, int b) {
    return a + b;
}
int pls(int a, int b) {
    return a * b;
}

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // 子进程
        int i = 5;
        while (i--)
        {
            printf("我是子进程, 我的PID: %d, 我的PPID:%d\n", getpid(), getppid());
            sleep(2);
        }
        exit(0);
    }
    // 父进程
    // 基于非阻塞的轮询等待方案
    int status = 0;
    int i = 1, j = 2;
    while (1)
    {
        pid_t ret = waitpid(id, &status, WNOHANG);
        //正常返回子进程
        if (ret > 0)
        {
            printf("等待成功, %d, exit code: %d, exit sig: %d\n", ret, WIFEXITED(status), WTERMSIG(status));
            break;
        }
        //发现没有已退出的子进程可收集
        else if (ret == 0)
        {
            //等待成功了,但子进程没有退出
            printf("子进程好了没?没有,父进程做其他事情\n");
            printf("add %d  ", add(i++, j++));
            printf("pls %d\n", pls(i++, j++));
            sleep(1);
        }
        else {
            //err
            printf("父进程等待出错!\n");
            break;
        }
    }
    return 0;
}

输出结果:

在这里插入图片描述

💎四、进程替换(重点)

子进程都是运行的已经预先写好的代码,可能用的是C++,现在希望子进程调用其他程序,而且其他程序可能是用其他语言写的,就要用到程序替换

🏆1.替换原理

  • 将磁盘中的程序加载进入内核结构
  • 重新建立页表映射,谁执行程序替换,就建立谁的映射,如果是子进程调用的程序替换,那么就会修改子进程的页表映射
  • 效果:子进程代码和父进程彻底分离,子进程执行了一个全新的程序

这个过程中并没有创建新的子进程,本质上还是当前子进程

🏆2.替换函数-记得包含头文件<unistd.h>

在这里插入图片描述

execl

参数

int execl(const char *path, const char *arg, ...);
  • path是需要运行程序的路径
  • arg代表需要执行的程序
  • ...是可变参数,可以传入不定量的参数,这里我们填入的是命令行的参数,填入命令行参数的时候,必须要以NULL作为参数的结尾
#include<stdlib.h>
#include<stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    printf("开始测试\n\n");
    execl("/usr/bin/ls","ls","-l",NULL);
    //execl("/home/Jungle/lesson15_进程控制","gcc","mytest.c",NULL);//调程序也是一样的
    printf("执行结束\n");
    return 0;
}

输出结果:

在这里插入图片描述

调用了其他可执行程序之后,第二个printf函数并没有被执行,因为用这个函数来调用其他可执行程序,本质上已经把当前的代码和数据替换掉了,那么原本的printf("执行结束 %d\n",ret);肯定也不会执行

返回值

程序正常执行,不会有返回值,只有出错的时候才会返回-1,同时会更新ERRNO,比如将 /usr/bin/ls 修改,下面程序就会报错,没有这个abc文件

#include<stdlib.h>
#include<stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    printf("开始测试\n\n");
    int ret = execl("/usr/bin/abc","ls","-l",NULL);
    printf("执行结束: %d\n",ret);
    printf("错误原因: %s\n",strerror(errno));
    return 0;
}

execv

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

需要我们用一个指针数组来传入命令行参数,其他都和execl一样

#include<stdlib.h>
#include<stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    printf("开始测试\n\n");
    char*const arg[]={
        "ls",
        "-l",
        "-a",
        NULL
    };
	execv("/usr/bin/ls",arg);
    printf("错误原因: %s\n",strerror(errno));
    return 0;
}

execlp

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

参数的说明从path变成了file

这个函数和execl的区别在于,它会自己去系统环境变量的PATH里面查找可执行程序,

下面程序出现两个ls,含义不一样,第一个代表文件,第二个代表以怎样的方式打开

#include<stdlib.h>
#include<stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    printf("开始测试\n\n");
	execlp("ls","ls","-l",NULL);
    return 0;
}

execvp

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

和execlp只有传参的区别

#include<stdlib.h>
#include<stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    printf("开始测试\n\n");
    char*const arg[]={
        "ls",
        "-l",
        "-a",
        NULL
    };
	execvp("ls",arg);
    return 0;
}

execle和execve

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

作用:利用可变参数传入命令行参数和利用数组传入命令行参数

参数:可执行文件的完整路径,命令行参数,环境变量e代表的是环境变量,代表我们可以把特定的环境变量传入其中进行处理。它们的环境变量都是在最末尾传的

用C程序调用C++程序打印换进变量

C++程序,mytest.cpp

#include <iostream>
#include <stdlib.h>
using namespace std;

int main()
{
    cout << "hello c++" << endl;
    cout << "-------------------------------------------\n";
    cout << "PATH:" << getenv("PATH") << endl;
    cout << "-------------------------------------------\n";
    cout << "MYPATH:" << getenv("MYPATH") << endl;
    cout << "-------------------------------------------\n";
    
    return 0;
}

execle调用

#include<stdlib.h>
#include<stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    extern char ** environ;//环境变量指针声明
    printf("开始测试\n\n");
	execle("./mytest","mytest","NULL",environ);//mytest是可执行程序
    return 0;
}

execve调用

#include<stdlib.h>
#include<stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    extern char ** environ;//环境变量指针声明
    printf("开始测试\n\n");
    char*const arg[]={
        "./mytest",
        NULL
    };
    execve("./mytest",arg,environ);
    return 0;
}

打印结果不会打印MYPATH,是因为环境变量中没有MYPATH

导入环境变量

用数组来传入环境变量,自己定义环境变量的时候记得带上系统环境变量,传入的时候,是会覆盖掉系统的环境变量的

#include<stdlib.h>
#include<stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    extern char ** environ;//环境变量指针声明
    printf("开始测试\n\n");
    char*const arg[]={
        "./mytest",
        NULL
    };
    char*const env[]={
        "PATH=path test",//记得带上系统中的环境变量
        "MYPATH=this is c test",
        NULL
    };
    execve("./mytest",arg,env);
    return 0;
}

系统接口execve

实际上只有execve是Linux系统提供的接口,其他函数都是C语言库中对execve的二次封装,来适应不同的使用场景

🏆3.函数命名总结

  • l(list):使用可变参数列表
  • v(vector):用数组传参
  • p(path):自动在环境变量PATH中搜索
  • e(env):表示自己维护环境变量

💎五、做一个shell

shell也就是命令行解释器,其运行原理就是:当有命令需要执行时,shell创建子进程,让子进程执行命令,而shell只需等待子进程退出即可。

在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
 
#define NUM 1024
#define CMD_NUM 128
#define SEP " "
char command[NUM];
char *argv[CMD_NUM]={NULL};//存放各个命令

int main()
{
  while(1)
  {
    char *argv[CMD_NUM]={NULL};//存放各个命令
 
    //1.打印提示符
    printf("[Jungle@VM-20-8-centos:~]$");
    fflush(stdout);//输出缓冲区内容
 
    //2.获取用户输入
    memset(command, sizeof(char), sizeof(char)*sizeof(command));
    fgets(command,NUM,stdin);
    command[strlen(command)-1]='\0';//清空'\n'
    
    //3.解析命令字符串
    //"ls -a -l -i\0";->"ls" "-a" "-l" "-i"
    argv[0]=strtok(command,SEP);//将字符串按sep分隔符分开
    int i=1;
    //strtok截取成功返回字符串起始地址,截取失败返回NULL
    while(argv[i]=strtok(NULL,SEP))
    {
      i++;
    }
 
    //4.TODO,检测命令是否是需要shell本身执行的,内建命令
    if(strcmp(argv[0],"cd")==0 && argv[1]!=NULL)//如果调用的是cd命令
    {
      chdir(argv[1]);//chdir改变当前工作目录,父进程
      continue;
    }
    if(strcmp(argv[0],"export")==0 && argv[1]!=NULL)//如果调用的是export命令
    {
      putenv(argv[1]);//putenv导入环境变量
      continue;
    }
 
    //5.创建进程,执行
    pid_t id = fork();
    if(id==0)//child
    {
      //6.程序替换
      execvp(argv[0],argv);//第一个参数保存的是我们需要执行的程序的名字
      exit(1);//替换失败
    }
    //parent
    int status = 0;
    pid_t ret = waitpid(id,&status,0);
    if(WIFEXITED(status))//子进程正常退出返回真
    {   
        printf("等待成功,子进程pid:%d, 状态:%d,信号:%d\n",ret,WEXITSTATUS(status),WTERMSIG(status));
    }
    else
    {
        printf("非正常退出,子进程pid:%d, 状态:%d,信号:%d\n",ret,WEXITSTATUS(status),WTERMSIG(status));
    }
  }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【路径规划-多式联运】基于遗传算法求解多式联运运输问题(考虑碳交易)附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

字符串和编码那些事

一、字符编码 1. ASCII字符编码 因为计算机只能处理数字&#xff0c;如果要处理文本&#xff0c;就必须先把文本转换为数字才能处理。最早的计算机在设计时采用8个比特&#xff08;bit&#xff09;作为一个字节&#xff08;byte&#xff09; 由于计算机是美国人发明的&#…

【Apache Spark 】第 11 章使用 Apache Spark 管理、部署和扩展机器学习管道

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

编程神器Copilot逐字抄袭他人代码?

自面世后就饱受争议的 GitHub Copilot 编程神器最近又遭遇舆论风暴。 日前,德州农工大学的一位计算机科学教授 Tim Davis 在推特上发文称, GitHub Copilot 在没有标注来源也没有 LGPL 许可的情况下,输出了大量应该受版权保护的代码。 Tim Davis 还发了自己和 GitHub Copil…

90后汕头返种水稻 国稻种芯·中国水稻会:广东新农人田保姆

90后汕头返种水稻 国稻种芯中国水稻会&#xff1a;广东新农人田保姆 南方日报 张伟炜 新闻中国采编网 中国新闻采编网 谋定研究中国智库网 中国农民丰收节国际贸易促进会 国稻种芯中国水稻节 中国三农智库网-功能性农业农业大健康大会报道&#xff1a;“5月稻谷病虫害防护非常…

机器学习(周志华)课后习题

第1章 绪论 1.1 表1.1若只包含编号1和4的两个样例&#xff0c;试给出相应的版本空间。 版本空间&#xff1a;与训练及一致的假设集合。 色泽青绿&#xff0c;根蒂*&#xff0c;敲声*&#xff1b; 色泽*&#xff0c;根蒂蜷缩&#xff0c;敲声*&#xff1b; 色泽*&#xff0c;根…

nuxt.js 进行项目重构-首页

nuxt.js 也是基于vue 的 那么就离不开组件化开发 我们按照组件结构来进行分析 navTop 页面的头部 通用组件 分隔了三个位置 适用于大多数头部 且预留插槽 <template><div class"nav-top"><div class"left"><slot name"left…

Spring5入门到实战------10、操作术语解释--Aspectj注解开发实例。AOP切面编程的实际应用

1、操作术语 1.1、连接点 类里面哪些方法可以被增强、这些方法被称为连接点。比如&#xff1a;用户控制层有登录、注册、修改密码、修改信息等方法。假如只有登录类和注册类可以被增强&#xff0c;登录和注册方法就称为连接点 1.2、切入点 实际被真正增强的方法&#xff0c…

C++ 【UVA488】Triangle Wave

&#x1f4cb; 个人简介 &#x1f496;大家好&#xff0c;我是2022年3月份新人榜排名第三的 ༺Blog༒Hacker༻ &#x1f389;支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4ac;格言&#xff1a;༺永做优质༒programmer༻ &#x1f4e3; 系列专栏&am…

Unity技术手册-编辑器基础入门万字大总结

往期文章分享点击跳转>《导航贴》- Unity手册&#xff0c;系统实战学习点击跳转>《导航贴》- Android手册&#xff0c;重温移动开发 本文约8千字&#xff0c;新手阅读需要20分钟&#xff0c;复习需要12分钟 【收藏随时查阅不再迷路】 &#x1f449;关于作者 众所周知&…

【C/C++】程序环境,探索程序的执行过程(习得无上内功《易筋经》的第一步)

目录1.程序的翻译环境和执行环境2.详解编译链接2.1翻译环境2.2编译本身也分为几个阶段预编译&#xff08;预处理&#xff09;编译汇编详解符号表形成符号表2.3.链接合并段表符号表的合并和重定位3.运行环境总结&#xff1a;1.程序的翻译环境和执行环境 在ANSIC&#xff08;标准…

LeetCode每日一题——1235. 规划兼职工作

LeetCode每日一题系列 题目&#xff1a;1235. 规划兼职工作 难度&#xff1a;困难 文章目录LeetCode每日一题系列题目示例思路题解题目 你打算利用空闲时间来做兼职工作赚些零花钱。 这里有 n 份兼职工作&#xff0c;每份工作预计从 startTime[i] 开始到 endTime[i] 结束&a…

1024程序员节|基于Springboot实现爱心捐赠管理系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 文末获取源码 项目编号&#xff1a;BS-XX-…

Mybatis-plus学习(基于版本3.0.5)

文章目录一.概念1.1 简介1.2 特性二.快速入门三.CRUD扩展3.1 Insert插入3.2 主键生成策略3.3 Update更新3.4 自动填充3.5 乐观锁3.6 查询操作3.7 删除操作3.8 性能分析插件&#xff08;新版本的Mybatis-plus已将此插件移除&#xff09;3.9 条件构造器3.10 代码生成器一.概念 1…

Transformer合集3

太多了 我都累了 这都第4了 这次先是关于他的小样本目标检测 , 用很少的训练示例检测新目标 小样本目标检测 论文地址&#xff1a; https://openaccess.thecvf.com/content/CVPR2022/papers/Han_Few-Shot_Object_Detection_With_Fully_Cross-Transformer_CVPR_2022_paper.…

docker安装influxdb及备份恢复

influxdb安装influxdb1&#xff0c;拉取镜像2&#xff0c;创建目录并进入到目录内3&#xff0c;创建influxdb容器服务4&#xff0c;访问&#xff1a;ip8086备份恢复influxdb数据准备1.1 创建用户&#xff0c;填入组织&#xff0c;桶信息1.2&#xff0c;给桶添加点数据1&#xf…

ansible部署lnmp架构

环境准备&#xff1a; 主机名IP服务系统ansible192.168.160.131ansibleCentOS-8.5nginx192.168.160.132nginxCentOS-8.5mysql192.168.160.137mysqlCentOS-8.5php192.168.160.139phpCentOS-8.5 1、生成私钥&#xff0c;对另外三台主机进行免密登入 [rootansible ~]# ssh-keyge…

【单片机毕业设计】【mcuclub-jj-007】基于单片机的门铃的设计

最近设计了一个项目基于单片机的门铃&#xff0c;与大家分享一下&#xff1a; 一、基本介绍 项目名&#xff1a;门铃 项目编号&#xff1a;mcuclub-jj-007 单片机类型&#xff1a;STC89C52、STM32F103C8T6 具体功能&#xff1a; 1、通过人体热释电检测是否有人&#xff0c;当…

Java --- 创建SpringMVC项目

目录 一、什么是MVC 二、什么是SpringMVC 三、SpringMVC的特点 四、创建SpringMVC项目 4.1、开发环境 4.2、创建maven工程 4.3、配置web.xml文件 4.4、创建请求控制器 4.5、配置springMVC.xml文件 4.5、访问首页面 4.6、访问指定页面 一、什么是MVC MVC是一种软件架…

C++:C++的IO流

while (scanf("%s", buff) ! EOF)如何终止&#xff1f; 答&#xff1a;ctrl z换行 是规定&#xff0c;ctrl c 是发送信号杀死进程&#xff08;一般不建议ctrl c&#xff09;。 int main() {string str;while (cin >> str) // operator>>(cin, str){cou…