Linux第六讲:进程控制

Linux第六讲:进程控制

  • 1.进程创建
    • 1.1回顾fork
    • 1.2写时拷贝
  • 2.进程终止
    • 2.1exit与_exit
  • 3.进程等待
    • 3.1进程等待的方法(wait和waitpid)
  • 4.进程程序替换
    • 4.1自定义shell的编写
      • 4.1.1输出命令行提示符
      • 4.1.2获取用户输入的命令
      • 4.1.3命令行分析
      • 4.1.4指令执行
      • 4.1.5问题演示
        • 4.1.5.1cd内建命令处理
        • 4.1.5.2环境变量表的实现
        • 4.1.5.3echo内建命令的实现
  • 5.自定义shell完整代码实现

1.进程创建

1.1回顾fork

fork之前已经讲过了,所以这里我们就简单回顾一下fork:
fork:创建子进程,父进程返回父进程的pid,子进程返回0,失败返回-1
总结fork内核(OS)工作原理:
1.为子进程分配PCB和页表,以及内存块
2.将父进程的部分数据(页表数据、PCB数据)拷贝给子进程
3.将子进程添加到系统的进程列表中
4.fork返回,开始调度器调度

#include <unistd.h>
pid_t fork(void);
返回值:⾃进程中返回0,⽗进程返回⼦进程id,出错返回-1

1.2写时拷贝

1.写时拷贝的原理为:
子进程拷贝父进程页表之后,OS会将父进程和子进程所有的代码块的权限设置为只读权限,子进程修改数据时,只读权限,操作系统会发生报错(不是真的报错),有多种情况需要分析,当OS分析,有虚拟地址,有物理地址,访问的数据是数据段的内容,不是代码段的内容,那么就触发写时拷贝,其它情况(缺页中断)就有其它的解决方案
2.写时拷贝的好处有:
减少子进程的创建时间、减少空间浪费

在这里插入图片描述

2.进程终止

进程退出有三种情况:
1.进程正常执行,结果正常
2.进程正常执行,结果错误
3.进程异常终止
而进程的结果要被父进程拿到,需要靠退出码,退出码为0表示正常,退出码为非0表示不同的出错原因(echo $?可以查看最近一次进程的退出码

在这里插入图片描述
我们可以使用strerror拿到所有的退出码对应的错误信息:
在这里插入图片描述
在这里插入图片描述

2.1exit与_exit

我们不只可以使用return来终止进程,还可以使用exit和_exit来终止进程,他们两个被放在任意位置都可以终止进程,但是它们的区别在于exit是C标准库中的函数,_exit是系统调用的函数,exit里面封装了_exit,调用_exit之前会对缓冲区进行刷新

在这里插入图片描述

3.进程等待

为什么需要进程等待:
1.子进程退出之后,如果父进程什么也不做,就会导致僵尸进程(只有子进程的PCB),进而导致内存泄漏,而kill -9也无法杀掉该子进程,因为该进程已经是死去的进程了
2.父进程需要关心子进程的执行结果,获取子进程的退出信息
3.这是最重要的一点:也就是需要回收子进程的资源

3.1进程等待的方法(wait和waitpid)

wait: pid_t wait(int* status)
1.等待任意一个子进程的结束,成功后,解决子进程的僵尸问题,并返回子进程的pid,失败,返回-1
2.如果等待子进程,父进程会阻塞在wait调用处
waitpid: pid_t waitpid(pid_t pid, int* status, int options)
1.pid:-1表示等待任意的子进程,>1表示等待特定pid的子进程
2.status:输出型参数,通常只有低16位保存有信息,高8位:退出码(错误码),第7位: core dump,其余7位:退出状态(终止信号),需要注意的是对于退出码和终止信号的提取(位运算或者是宏)
3.options:两个选项:1.0,默认为阻塞调用 2.NOHANG,表示非阻塞调用

在这里插入图片描述

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

//函数指针类型
typedef void (*func_t)();

#define NUM 5
func_t handlers[NUM];

//注册
void registerHandler(func_t h[], func_t t)
{
    int i = 0;
    for( ; i<NUM; i++)
    {
        if(h[i] == NULL) break;
    }
    h[i] = t;
    if(i == NUM) return;
}

//下面是任务
void Download()
{
    printf("这是一个下载任务\n");
}
void Flush()
{
    printf("这是一个刷新任务\n");
}
void Log()
{
    printf("这是一个记录日志任务\n");
}

int main()
{
    registerHandler(handlers, Download);
    registerHandler(handlers, Flush);
    registerHandler(handlers, Log);

    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        int cnt = 3;
        while(cnt--)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        //int b = 1/0;
        exit(13);
    }
    //父进程
    while(1)
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, WNOHANG);
        if(rid > 0)
        {
            //printf("wait success: id: %d, exit code: %d, exit singal: %d\n", rid, (status>>8)&0xFF, status&0x7F);
            //printf("wait success: id: %d, exit code: %d, exit singal: %d\n", rid, (status>>8)&0xFF, WIFEXITED(status));
            printf("wait success: id: %d, exit code: %d, exit singal: %d\n", rid, WEXITSTATUS(status), WIFEXITED(status));
            printf("%d\n", status);
        }
        else if(rid == 0)
        {
            int i = 0;
            for( ; handlers[i]; i++)
            {
                handlers[i]();
            }
            printf("本轮调用结束,子进程没有退出\n");
            sleep(1);
        }
        else
        {
            //printf("wait success: id: %d, exit code: %d, exit singal: %d\n", rid, (status>>8)&0xFF, status&0x7F);
            printf("wait unsuccess: rid: %d\n", rid);
        }
    }

    return 0;
}

4.进程程序替换

每一个进程都有它的PCB和它的代码和数据,而进程程序替换的本质就是将需要进行替换的进程的代码和数据与现在正在进行的进程的代码和数据进行替换,所以说,一旦发生了进程程序替换,那么原始的代码和数据就被替换,不复存在了,下面我们讲一下关于进程程序替换相关的exec系列函数,注意:exec系列函数只有出错时才会有返回值,返回值为-1,但是一般不需要作返回值判断,因为如果进行了返回,那么就是出错了:

在这里插入图片描述

4.1自定义shell的编写

4.1.1输出命令行提示符

我们自己的shell中,输入指令之前都会给出一个提示符,所以我们自己实现的shell也要初始命令行提示符:
在这里插入图片描述
对于这些信息,可以在env中找到:

//输出命令行提示符
#define COMMAND_SIZE 1024 //假设命令行可以传入1024字节内容
#define FORMAT "[%s@%s %s]# " //snprintf输出格式
const char* GetUserName()
{
    const char* ret = getenv("USER");
    return ret == NULL ? "NULL" : ret;
}
const char* GetHostName()
{
    const char* ret = getenv("HOSTNAME");
    return ret == NULL ? "NULL" : ret;
}
const char* GetPWD()
{
    const char* ret = getenv("PWD");
    return ret == NULL ? "NULL" : ret;
}

//向out数组中写入命令行提示字符串
void MakeCommandLine(char* out, int size)
{
    //snprintf(char *str, size_t size, const char *format, ...); 
    snprintf(out, size, FORMAT, GetUserName(), GetHostName(), GetPWD());
}

void PrintCommandPrompt()
{
    char commandline[COMMAND_SIZE];
    MakeCommandLine(commandline, sizeof(commandline));
    printf("%s", commandline); 
}

int main()
{
    //1.输出命令行提示符
    PrintCommandPrompt();

    return 0;
}

4.1.2获取用户输入的命令

我们需要直到用户想要执行什么命令,才能够进行输出

//2.获取用户输入的命令,将输入的命令,填写到数组中
bool GetCommandLine(char* out, int size)
{
    char* c = fgets(out, size, stdin);//不能使用scanf
    if(c == NULL) return false;
    out[strlen(out)-1] = 0;//清理最后一个\n,否则输入之后会自动换行
    if(strlen(out) == 0) return false;
    return true;
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();
        
        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline, sizeof(commandline))) continue;//如果获取失败,重新获取
    }

    return 0;
}

4.1.3命令行分析

知道用户输入什么命令之后,我们需要对用户输入的命令进行解析:

//3.命令行解析
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc;
bool CommandPrase(char* commandline)
{
#define SEP " "
    g_argc = 0;
    g_argv[g_argc++] = strtok(commandline, SEP);//strtok的作用为将commandline数组中的内容以SEP字符分割
    while((bool)(g_argv[g_argc++] = strtok(NULL, SEP)));//之后传入null继续分割
    g_argc--;
    return true;
}

//打印输入的命令是否正确
void Print()
{
    for(int i = 0; i<g_argc; i++)
    {
        printf("g_argv[%d]:%s\n", i, g_argv[i]);
    }
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();
        
        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline, sizeof(commandline))) continue;//如果获取失败,重新获取
        
        //3.命令行解析
        CommandPrase(commandline);
        Print();
    }

    return 0;
}

4.1.4指令执行

拿到了指令之后,需要执行指令,执行指令需要用到之前学过的exec系列接口了,那么应该选择哪一种接口呢?1.有了argv表,首先排除带l的接口,2.不关心env,排除e接口,3.不需要传入完整的路径,所以最终选择execvp进行指令的执行:

//4.指令的执行
int Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        execvp(g_argv[0], g_argv);
        exit(1);
    }
    pid_t rid = waitpid(-1, NULL, 0);
    (void)rid;//防止提示,使用一下
    return 0;
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();
        
        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline, sizeof(commandline))) continue;//如果获取失败,重新获取
        
        //3.命令行解析
        CommandPrase(commandline);
        //Print();

        //4.指令的执行
        Execute();
    }

    return 0;
}

4.1.5问题演示

写出的myshell并没有什么大的问题,但是有的指令并不能正常执行:
在这里插入图片描述
cd、echo等命令属于内建命令,内建命令需要shell自己处理,所以对于内建命令,必须要进行特殊处理:

4.1.5.1cd内建命令处理
//4.内建命令特殊处理
const char* GetHome()
{
    const char* ret = getenv("HOME");
    return ret == NULL ? "" : ret;
}

bool Cd()
{
    if(g_argc == 1) 
    {
        std::string home = GetHome();
        if(home.empty()) return true;
        chdir(home.c_str());
        return true;
    }
    else
    {
        std::string where = g_argv[1];
        if(where == "~")
        {

        }
        else if(where == "-")
        {
            
        }
        else 
        {
            chdir(where.c_str());
        }
        return true;
    }

    return false;
}

void Echo()
{

}

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        Cd();
        return true;
    }
    else if(cmd == "echo")
    {
        Echo();
        return true;
    }
    else if(cmd == "export")
    {

    }
    else if(cmd == "alias")
    {

    }

    return false;
}

int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();
        
        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline, sizeof(commandline))) continue;//如果获取失败,重新获取
        
        //3.命令行解析
        CommandPrase(commandline);
        //Print();

        //4.对于内建命令,要进行特殊处理
        if(CheckAndExecBuiltin()) continue;//如果是内建命令的话,就不需要下面的指令执行了

        //5.指令的执行
        Execute();
    }

    return 0;
}

在这里插入图片描述
可以看到,确实是切换了路径,但是命令行提示符并没有发生改变,而且使用env进行对比时,env也并没有发生变化,原因是:调用chdir命令之后,并没有对环境变量进行更新,导致拿到的pwd还是旧的环境变量,所以我们需要进行环境变量的更新操作:

char cwd[1024];
char cwdenv[1024];
const char* GetPWD()
{
    //consti char* ret = getenv("PWD");
    const char* ret = getcwd(cwd, sizeof(cwd));//函数作用为将现在的pwd写入到cwd数组中
    if(ret != NULL)
    {
    	//更新环境变量
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", ret);
        putenv(cwdenv);
    }
    return ret == NULL ? "NULL" : ret;
}
4.1.5.2环境变量表的实现

shell中有自己的命令行参数表,我们已经实现过了(g_argc, g_argv[]),这张表可以让子进程拿到(因为是全局的),子进程就可以根据拿到的命令行指令执行相应的操作。还应该有一张环境变量表,这样每一个子进程都可以拿到环境变量了,即使不进行环境变量的传递:

//0.环境变量表的实现
#define MAX_ENVS 100
char* g_env[MAX_ENVS];
int g_envs = 0;
//对环境变量表进行初始化
void Init_ENV()
{
    //本来是需要在配置文件中拿到环境变量的,但是我们只需要知道怎么从父进程拿到即可
    extern char** environ;
    memset(g_env, 0, sizeof(g_env));
    g_envs = 0;
    //1.获取环境变量
    for(int i = 0; environ[i]; i++)
    {
        g_env[i] = (char*)malloc(strlen(environ[i])+1);
        strcpy(g_env[i], environ[i]);
        g_envs++;
    }
    g_env[g_envs] = NULL;
    //2.导入环境变量
    for(int i = 0; g_env[i]; i++)
    {
        putenv(g_env[i]);
    }
    environ = g_env;
}
4.1.5.3echo内建命令的实现
int lastcode = 0;//保存最近一次进程的结束码
void Echo()
{
    if(g_argc == 2)
    {
        // echo "hello world"
        // echo $?
        // echo $PATH
        std::string opt = g_argv[1];
        if(opt == "$?")
        {
            std::cout << lastcode << std::endl;
            lastcode = 0;
        }
        else if(opt[0] == '$')
        {
            std::string env_name = opt.substr(1);
            const char *env_value = getenv(env_name.c_str());
            if(env_value)
                std::cout << env_value << std::endl;
        }
        else
        {
            std::cout << opt << std::endl;
        }
    } 
}

//5.指令的执行
int Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        execvp(g_argv[0], g_argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(-1, &status, 0);
    if(rid > 0)
    {
        lastcode = WEXITSTATUS(status);//这里要更新退出码
    }
    return 0;
}

5.自定义shell完整代码实现

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

//0.环境变量表的实现
#define MAX_ENVS 100
char* g_env[MAX_ENVS];
int g_envs = 0;
//对环境变量表进行初始化
void Init_ENV()
{
    //本来是需要在配置文件中拿到环境变量的,但是我们只需要知道怎么从父进程拿到即可
    extern char** environ;
    memset(g_env, 0, sizeof(g_env));
    g_envs = 0;
    //1.获取环境变量
    for(int i = 0; environ[i]; i++)
    {
        g_env[i] = (char*)malloc(strlen(environ[i])+1);
        strcpy(g_env[i], environ[i]);
        g_envs++;
    }
    g_env[g_envs] = NULL;
    //2.导入环境变量
    for(int i = 0; g_env[i]; i++)
    {
        putenv(g_env[i]);
    }
    environ = g_env;
}

//1.输出命令行提示符
#define COMMAND_SIZE 1024 //假设命令行可以传入1024字节内容
#define FORMAT "[%s@%s %s]# " //snprintf输出格式
const char* GetUserName()
{
    const char* ret = getenv("USER");
    return ret == NULL ? "NULL" : ret;
}
const char* GetHostName()
{
    const char* ret = getenv("HOSTNAME");
    return ret == NULL ? "NULL" : ret;
}
char cwd[1024];
char cwdenv[1024];
const char* GetPWD()
{
    //consti char* ret = getenv("PWD");
    const char* ret = getcwd(cwd, sizeof(cwd));//函数作用为将现在的pwd写入到cwd数组中
    if(ret != NULL)
    {
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", ret);
        putenv(cwdenv);
    }
    return ret == NULL ? "NULL" : ret;
}

//截取pwd,否则命令行提示的pwd显示太多
std::string DirName(const char* pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if(dir == SLASH) return "SLASH";
    auto pos = dir.rfind(SLASH);
    if(pos == std::string::npos) return "FALSE";
    return dir.substr(pos+1);
}

//向out数组中写入命令行提示字符串
void MakeCommandLine(char* out, int size)
{
    //snprintf(char *str, size_t size, const char *format, ...); 
    snprintf(out, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPWD()).c_str());
    //snprintf(out, size, FORMAT, GetUserName(), GetHostName(), GetPWD());
}

void PrintCommandPrompt()
{
    char commandline[COMMAND_SIZE];
    MakeCommandLine(commandline, sizeof(commandline));
    printf("%s", commandline); 
    fflush(stdout);
}

//2.获取用户输入的命令,将输入的命令,填写到数组中
bool GetCommandLine(char* out, int size)
{
    char* c = fgets(out, size, stdin);//不能使用scanf
    if(c == NULL) return false;
    out[strlen(out)-1] = 0;//清理最后一个\n,否则输入之后会自动换行
    if(strlen(out) == 0) return false;
    return true;
}

//3.命令行解析
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc;
bool CommandPrase(char* commandline)
{
#define SEP " "
    g_argc = 0;
    g_argv[g_argc++] = strtok(commandline, SEP);//strtok的作用为将commandline数组中的内容以SEP字符分割
    while((bool)(g_argv[g_argc++] = strtok(NULL, SEP)));//之后传入null继续分割
    g_argc--;
    return true;
}

//打印输入的命令是否正确
void Print()
{
    for(int i = 0; i<g_argc; i++)
    {
        printf("g_argv[%d]:%s\n", i, g_argv[i]);
    }
}

//4.内建命令特殊处理
const char* GetHome()
{
    const char* ret = getenv("HOME");
    return ret == NULL ? "" : ret;
}

bool Cd()
{
    if(g_argc == 1) 
    {
        std::string home = GetHome();
        if(home.empty()) return true;
        chdir(home.c_str());
        return true;
    }
    else
    {
        std::string where = g_argv[1];
        if(where == "~")
        {

        }
        else if(where == "-")
        {
            
        }
        else 
        {
            chdir(where.c_str());
        }
        return true;
    }

    return false;
}

int lastcode = 0;//保存最近一次进程的结束码
void Echo()
{
    if(g_argc == 2)
    {
        // echo "hello world"
        // echo $?
        // echo $PATH
        std::string opt = g_argv[1];
        if(opt == "$?")
        {
            std::cout << lastcode << std::endl;
            lastcode = 0;
        }
        else if(opt[0] == '$')
        {
            std::string env_name = opt.substr(1);
            const char *env_value = getenv(env_name.c_str());
            if(env_value)
                std::cout << env_value << std::endl;
        }
        else
        {
            std::cout << opt << std::endl;
        }
    } 
}

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        Cd();
        return true;
    }
    else if(cmd == "echo")
    {
        Echo();
        return true;
    }
    else if(cmd == "export")
    {

    }
    else if(cmd == "alias")
    {

    }

    return false;
}

//5.指令的执行
int Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        execvp(g_argv[0], g_argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(-1, &status, 0);
    if(rid > 0)
    {
        lastcode = WEXITSTATUS(status);
    }
    return 0;
}

int main()
{
    Init_ENV();
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();
        
        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline, sizeof(commandline))) continue;//如果获取失败,重新获取
        
        //3.命令行解析
        CommandPrase(commandline);
        //Print();

        //4.对于内建命令,要进行特殊处理
        if(CheckAndExecBuiltin()) continue;//如果是内建命令的话,就不需要下面的指令执行了

        //5.指令的执行
        Execute();
    }

    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

BI 工具响应慢?可能是 OLAP 层拖了后腿

在数据驱动决策的时代&#xff0c;BI 已成为企业洞察业务、辅助决策的必备工具。然而&#xff0c;随着数据量激增和分析需求复杂化&#xff0c;BI 系统“卡”、“响应慢”的问题日益突出&#xff0c;严重影响分析效率和用户体验。 本文将深入 BI 性能问题的根源&#xff0c;并…

PPT内视频播放无法播放的原因及解决办法

PPT内视频无法播放&#xff0c;通常是视频编解码的问题。目前我遇到的常见的视频编码格式有H.264&#xff0c;H.265&#xff0c;VP9&#xff0c;AV1这4种。H.264编解码的视频&#xff0c;Windows原生系统可以直接播放&#xff0c;其他的视频编码格式需要安装对应的视频编解码插…

【AIGC系列】6:HunyuanVideo视频生成模型部署和代码分析

AIGC系列博文&#xff1a; 【AIGC系列】1&#xff1a;自编码器&#xff08;AutoEncoder, AE&#xff09; 【AIGC系列】2&#xff1a;DALLE 2模型介绍&#xff08;内含扩散模型介绍&#xff09; 【AIGC系列】3&#xff1a;Stable Diffusion模型原理介绍 【AIGC系列】4&#xff1…

Navigation的进阶知识与拦截器配置

Navigation的进阶知识与拦截器配置 写的不是很详细&#xff0c;后续有时间会补充&#xff0c;建议参考官方文档食用 1.如何配置路由信息 1.1 创建工程结构 src/main/ets ├── pages │ └── navigation │ ├── views │ │ ├── Mine.ets //…

多模态推理模型相关开源工作

多模态推理模型相关开源工作 1. 训练策略1.1 R1-V① 介绍② 训练流程③ 关键注意点④ 主要问题⑤ 是否可以去掉 KL 约束&#xff1f; 1.2 open-r1-multimodal① 介绍② 代码改进 1.3 VisualThinker-R1-Zero① 研究意义② 训练方法③ 结论④ 代码改进⑤ 其他发现 1.4 Efficient-…

LaTex安装流程(附安装包)LaTex超详细保姆级图文安装教程

文章目录 前言一、LaTex下载二、Texlive 2024安装教程三、Texstudio安装教程 前言 本安装流程将以清晰、易懂的方式&#xff0c;详细的价绍 LaTeX安装教程&#xff0c;助你顺利踏入专业排版的大门 。 一、LaTex下载 LaTeX 是由美国计算机科学家莱斯利・兰伯特&#xff08;Les…

Ultravox:融合whisper+llama实现audio2text交互

Ultravox是由Fixie AI开发的一种创新型多模态大语言模型,专为实时语音交互设计。与传统的语音交互系统不同,Ultravox无需单独的语音识别(ASR)阶段,可以直接理解文本和人类语音,实现更快速、更自然的交互体验。Ultravox v0.5在语音理解基准测试中超越了OpenAI的GPT-4o Realt…

KL散度详解与应用

前言 本文隶属于专栏《机器学习数学通关指南》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见《机器学习数学通关指南》 ima 知识库 知识库广场搜索&#…

【Java并发】【synchronized】适合初学者体质入门的synchronized

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f44d; 欢迎点赞、收藏、关注&#xff0c;跟上我的更新节奏 &#x1f4da;欢迎订阅专栏…

数据库的搭建

一、MySQL的安装 第一种&#xff1a; 直接下载相应的软件&#xff1a; 比如说MySQL installer、或者phpstudy第二种&#xff1a; 1.压缩包下载 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 2.解压软件包 将MySQL软件包解压在没有中文和空格的目…

React:类组件(上)

kerwin老师我来了 类组件的创建 class组件&#xff0c;js里的类命名首字符大写&#xff0c;类里面包括构造函数&#xff0c;方法 组件类要继承React.Component才有效 必须包含render方法 import React from react class App extends React.Component{render() {return <…

以教育之道御AI之术:培养未来人才的关键策略

在当今这个人工智能(AI)技术日新月异的时代,AI已经渗透到我们生活的方方面面,教育领域也不例外。然而,面对AI的浪潮,我们不仅要学会利用它来提升教学效率,更要坚守教育的本质,即“以教育之道御AI之术”,培养出能够适应未来社会需求的创新型人才。 AI技术为教育带来的…

基于qiime2的16S数据分析全流程:从导入数据到下游分析一条龙

目录 创建metadata 把数据导入qiime2 去除引物序列 双端合并 &#xff08;dada2不需要&#xff09; 质控 &#xff08;dada2不需要&#xff09; 使用deblur获得特征序列 使用dada2生成代表序列与特征表 物种鉴定 可视化物种鉴定结果 构建进化树&#xff08;ITS一般不构建进化树…

DeepSeek V3 并行训练、推理优化点(一)

训练优化1&#xff0c; FP8计算 DeepSeek-V3在训练过程中统一使用E4M3格式&#xff0c;并通过细粒度的per-tile&#xff08;1x128&#xff09;和per-group&#xff08;128x128&#xff09;量化来降低误差。 FP8的好处还体现在节省显存上&#xff08;尤其是激活值&#xff09;…

comctl32!ListView_OnSetItem函数分析LISTSUBITEM结构中的image表示图标位置

第一部分&#xff1a; BOOL ListView_SetSubItem(LV* plv, const LV_ITEM* plvi) { LISTSUBITEM lsi; BOOL fChanged FALSE; int i; int idpa; HDPA hdpa; if (plvi->mask & ~(LVIF_DI_SETITEM | LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) { …

Docker基础篇——Ubuntu下Docker安装

大家好我是木木&#xff0c;在当今快速发展的云计算与云原生时代&#xff0c;容器化技术蓬勃兴起&#xff0c;Docker 作为实现容器化的主流工具之一&#xff0c;为开发者和运维人员带来了极大的便捷 。下面我们一起进行Docker安装。 Docker的官方Ubuntu安装文档&#xff0c;如…

Python图形编程之EasyGUI: indexbox的用法

目录<<上一章&#xff1a;ynbox用法详解 下一章&#xff1a;boolbox用法详解 >> # 1 Python图形编程之EasyGUI: indexbox的用法 1.1 基本用法 indexbox提供用户一个选择不同选项的功能&#xff0c;不同的选项由按钮来表示&#xff0c;提供类似功能的还有choicebox…

大语言模型从理论到实践(第二版)-学习笔记(绪论)

大语言模型的基本概念 1.理解语言是人工智能算法获取知识的前提 2.语言模型的目标就是对自然语言的概率分布建模 3.词汇表 V 上的语言模型&#xff0c;由函数 P(w1w2 wm) 表示&#xff0c;可以形式化地构建为词序列 w1w2 wm 的概率分布&#xff0c;表示词序列 w1w2 wm…

突破极限!蓝耘通义万相2.1引爆AI多模态新纪元——性能与应用全方位革新

云边有个稻草人-CSDN博客 目录 一、 引言 二、 蓝耘通义万相2.1版本概述 三、 蓝耘通义万相2.1的核心技术改进 【多模态数据处理】 【语音识别与文本转化】 【自然语言处理&#xff08;NLP&#xff09;改进】 【跨平台兼容性】 四、 蓝耘注册 部署流程—新手也能轻松…

JVM常用概念之本地内存跟踪

问题 Java应用启动或者运行过程中报“内存不足&#xff01;”&#xff0c;我们该怎么办? 基础知识 对于一个在本地机器运行的JVM应用而言&#xff0c;需要足够的内存来存储机器代码、堆元数据、类元数据、内存分析等数据结构&#xff0c;来保证JVM应用的成功启动以及未来平…