【Linux】第二十二站:文件(二)深入理解重定向

文章目录

  • 一、重定向
    • 1.文件描述符对应的分配规则
    • 2.重定向的接口
  • 二、再次实现myshell
    • 1.实现细节
    • 2.盘点文件与进程替换的一个细节
    • 3.代码
  • 三、1号文件和2号文件的区别
  • 四、如何理解“一切皆文件?”

一、重定向

1.文件描述符对应的分配规则

我们先看如下代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#define filename "log.txt"

int main()
{
    int fd = open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    printf("fd : %d\n",fd);
    const char* msg = "hello linux\n";
    int cnt = 5;
    while(cnt)
    {
        write(fd,msg,strlen(msg));
        cnt--;
    }

    close(fd);
    return 0;
}

运行结果为,一切都符合我们的预期

image-20231127162842345

紧接着,我们将代码改为如下

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#define filename "log.txt"

int main()
{
    close(0);
    int fd = open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    printf("fd : %d\n",fd);
    const char* msg = "hello linux\n";
    int cnt = 5;
    while(cnt)
    {
        write(fd,msg,strlen(msg));
        cnt--;
    }

    close(fd);
    return 0;
}

运行结果为

image-20231127165617384

如果我们将代码改为如下,即关闭一号文件

image-20231127165926548

那么运行结果为,将没有任何东西可以打印出来

image-20231127170010669

这是因为1号文件对应的是stout输出流,而printf里面是用到了这个流的。当我们关闭了以后,自然就出现问题了

如果我们关闭的是二号文件

image-20231127172002501

那么结果为

image-20231127172026414

我们可以发现如下现象

当我们关0的时候,为这个新文件分配的文件描述符是0

当我们关1的时候,为这个新文件分配的文件描述符是1

当我们关2的时候,为这个新文件分配的文件描述符是2

这就说明,文件描述符的分配规则很简单,从0下标开始,寻找最小的没有使用的数组位置,它的下标就是新文件的文件描述符

我们来看下面的代码

image-20231127172615148

最终运行结果为

image-20231127172650731

这是因为我们的东西并没有写到这个文件中,而是写入到了显示器文件中。所以才会打印出来。

我们再来看以下代码

image-20231127172836589

运行结果为

image-20231127172917076

这是因为,我们关闭了一号文件,而由于我们又打开了一个新文件,那么最终这个新文件的文件描述符变为了1。所以最终变为了向该文件写入

而这里,我们会发现,本来应该写入到显示器上的内容写入到了文件中,这不就是输出重定向吗?

如下图所示,是我们一开始的状态

image-20231127173921374

后来我们关闭了1号文件,然后打开了一个新的文件。就会将原来的引用计数减减,然后将该指针置空。随后我们创建新文件的时候,会让1号下标的位置指向log.txt这个文件中

image-20231127174302710

而在我们前面的代码中,上层并不知道我们已经将1号文件给改掉了。它只知道要向一号文件写,所以最终变为了向log.txt文件中去写

而上面所说的就是重定向的原理。

所以重定向的本质就是对文件描述符表里面的数组的内容进行修改

2.重定向的接口

我们会发现上面的方法其实有点麻烦,因为我们还需要关闭文件之后,才去打开一个新的文件。

所以操作系统本身就提供了系统调用

image-20231127175304336

int dup2(int oldfd, int newfd);

它的作用是直接将新的文件描述符表数组中的oldfd下标的内容直接拷贝到newfd处。即newfd是要被oldfd所覆盖的

如下图所示,fd代表的是oldfd,1代表的是newfd。

fd的内容最终被拷贝到1号的内容当中。最终保留的就是fd的内容

image-20231127181112181

所以我们就可以写出这个代码了

image-20231127181702696

运行结果为

image-20231127181723935

这样就同样实现了重定向的效果

如果我们将打开的方式换为了O_APPEND

image-20231127181908347

image-20231127181948257

我们会发现这个其实就是追加重定向

我们再来看下面的这段代码

注意这个接口的意思是:从fd中读取count字节个数据到buf中,count是期望读取的数量,返回值是实际读取的数量。

注意它的读取之后,最终不会加上’\0’字符,而fread是C语言的接口,它会自己加上的。所以我们最终需要自己加上这个’\0’字符。

ssize_t read(int fd, void *buf, size_t count);

代码为

image-20231127183759201

运行结果为

image-20231127183823732

然后我们让log.txt的内容如下

image-20231127183946696

代码如下

image-20231127184109563

运行结果为

image-20231127184155076

我们会发现直接读取了,因为我们当前的文件内本身就有内容,所以就默认从文件中读取了

我们会发现它就相当于输入重定向

image-20231127184425755

所以重定向的本质就是对进程的指定文件描述符表中内容拷贝的问题

如果我们的代码是这样子的

image-20231127195455195

运行结果为,符合我们的预期

image-20231127195516197

如果我们将代码改为这样的

image-20231127195817659

那么结果为

image-20231127195804453

他是符合我们的预期的

所以C语言的printf,和fprintf都是往1号文件里写的,不过我们已经提前改了一号文件了。所以就会显示如上的结果

如果我们改为O_APPEND

image-20231127200057332

那么结果也是一样的,符合我们的预期

image-20231127200123154

二、再次实现myshell

1.实现细节

我们知道,像我们平时在命令行中写的重定向是这样的

image-20231127200448554

那么它与我们前面所演示的重定向有什么关系呢?

我们知道,我们前面的代码中,myshell并没有实现重定向功能

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


#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44

char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];



const char* getusername()
{
    return getenv("USER");
}
const char* gethostname()
{
    return getenv("HOSTNAME");
}
void getpwd()
{
    getcwd(pwd,sizeof(pwd));
}

void Interact(char* cline,int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);
    char* s = fgets(cline,size,stdin);
    (void)s;
    assert(s);
    commandline[strlen(cline) - 1] = '\0';
}
int splitstring(char cline[],char* _argv[])
{
    if(strcmp(cline,"") == 0) return 0;
    int i = 0;
    _argv[i++] = strtok(cline,DELIM);
    while(_argv[i++] = strtok(NULL,DELIM));
    return i - 1;
}

void NormalExcute(char* _argv[])
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return;
    }
    else if (id == 0)
    {
        //子进程执行命令
        execvpe(_argv[0],_argv,environ);
        exit(EXIT_CODE);
    }
    else
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,0);
        if(rid == id)
        {
           last_code = WEXITSTATUS(status);
        }
    }
}

int BuildCommand(char* _argv[],int _argc)
{
    if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
    {
        chdir(_argv[1]);
        getpwd();
        sprintf(getenv("PWD"),"%s",pwd);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"export") == 0)
    {
        strcpy(myenv,_argv[1]);
        putenv(myenv);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)
    {
        if(strcmp(_argv[1],"$?") == 0)
        {
            printf("%d\n",last_code);
            last_code = 0;
        }
        else if(*_argv[1] == '$')
        {
            char* val = getenv(_argv[1] + 1);
            if(val) printf("%s\n",val);
        }
        else 
        {
            printf("%s\n",_argv[1]);
        }
        return 1;
    }


    if(_argc > 0 && strcmp(_argv[0],"ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}
int main()
{
    while(!quit)
    {
        //2.交互问题,解决命令行
        Interact(commandline,sizeof(commandline));
        //3.子串分割问题,解析命令行
        int argc = splitstring(commandline,argv);
        if(argc == 0) continue;
        //4.指令的判断(内建命令和普通命令)
        int n = BuildCommand(argv,argc);
        //5.普通命令的执行
        if(!n) NormalExcute(argv);
    }
    return 0;
}

那么我们现在可以为他添加上重定向功能,要解决重定向,我们可以交互函数函数中进行处理一下字符串,当该指令进行执行的时候,处理即可。

如下代码所示

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44
#define NONE       -1
#define IN_RDIR     0
#define OUT_RDIR    1
#define APPEND_RDIR 2

char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];
char* rdirfilename = NULL;
int rdir = NONE; 

const char* getusername()
{
    return getenv("USER");
}
const char* Gethostname()
{
    return getenv("HOSTNAME");
}
void getpwd()
{
    getcwd(pwd,sizeof(pwd));
}
void check_rdir(char* cmd)
{
    char* pos = cmd;
    while(*pos!='\0')
    {
        if(*pos == '>')
        {
            if(*(pos + 1) == '>')
            {
                *pos++ = '\0';
                *pos++ = '\0';
                while(isspace(*pos)) pos++;
                rdirfilename = pos;
                rdir=APPEND_RDIR;
                break;
            }
            else 
            {
                *pos = '\0';
                pos++;
                while(isspace(*pos)) pos++;
                rdirfilename = pos;
                rdir = OUT_RDIR;
                break;
            }
        }
        else if(*pos == '<')
        {
            *pos = '\0';  //ls -a -l < file.txt
            pos++;
            while(isspace(*pos)) pos++;
            rdirfilename = pos;
            rdir = IN_RDIR;
            break;
        }
        else 
        {
           // do nothing
        }
        pos++;
    }
}
void Interact(char* cline,int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),Gethostname(),pwd);
    char* s = fgets(cline,size,stdin);
    (void)s;
    assert(s);
    commandline[strlen(cline) - 1] = '\0';
    check_rdir(cline);
}
int splitstring(char cline[],char* _argv[])
{
    if(strcmp(cline,"") == 0) return 0;
    int i = 0;
    _argv[i++] = strtok(cline,DELIM);
    while(_argv[i++] = strtok(NULL,DELIM));
    return i - 1;
}

void NormalExcute(char* _argv[])
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return;
    }
    else if (id == 0)
    {
        int fd = 0;
        if(rdir == IN_RDIR)
        {
            fd = open(rdirfilename,O_RDONLY);
            dup2(fd,0);
        }
        else if(rdir == OUT_RDIR)
        {
            fd = open(rdirfilename,O_WRONLY|O_CREAT|O_TRUNC,0666);
            dup2(fd,1);
        }
        else if(rdir == APPEND_RDIR) 
        {
            fd = open(rdirfilename,O_WRONLY|O_CREAT|O_APPEND,0666);
            dup2(fd,1);
        }
        //子进程执行命令
        execvpe(_argv[0],_argv,environ);
        exit(EXIT_CODE);
    }
    else
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,0);
        if(rid == id)
        {
           last_code = WEXITSTATUS(status);
        }
    }
}

int BuildCommand(char* _argv[],int _argc)
{
    if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
    {
        chdir(_argv[1]);
        getpwd();
        sprintf(getenv("PWD"),"%s",pwd);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"export") == 0)
    {
        strcpy(myenv,_argv[1]);
        putenv(myenv);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)
    {
        if(strcmp(_argv[1],"$?") == 0)
        {
            printf("%d\n",last_code);
            last_code = 0;
        }
        else if(*_argv[1] == '$')
        {
            char* val = getenv(_argv[1] + 1);
            if(val) printf("%s\n",val);
        }
        else 
        {
            printf("%s\n",_argv[1]);
        }
        return 1;
    }


    if(_argc > 0 && strcmp(_argv[0],"ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}
int main()
{
    while(!quit)
    {
        rdirfilename = NULL;
        rdir = NONE;
        //2.交互问题,解决命令行
        Interact(commandline,sizeof(commandline));
        //3.子串分割问题,解析命令行
        int argc = splitstring(commandline,argv);
        if(argc == 0) continue;

        //4.指令的判断(内建命令和普通命令)
        int n = BuildCommand(argv,argc);
        //5.普通命令的执行
        if(!n) NormalExcute(argv);
    }
    return 0;
}

在上面的代码中,当我们输入完指令字符串以后,然后去寻找是否存在重定向的符号,如果有,改变当前状态为输入/输出/追加重定向,然后将重定向的文件名给记录下来。最终达到分开指令与文件的目的。

分开以后,当我们进行指令的执行的时候,我们暂时只考虑子进程,如果是存在重定向的话,那么就打开对应的文件,然后将该文件的文件描述符放到对应的输入或输出位置上。就可以了。

最终的结果为如下

image-20231128164958787

image-20231128165006593

2.盘点文件与进程替换的一个细节

我们在前面的代码中

在后面我们做了重定向的工作,后面我们在进行程序替换的时候,难道不会影响吗???

在我们之前,我们已经了解了如下的东西

当一个可执行程序加载到内存的时候,会创建出对应的PCB结构体,在tash_struct这个结构体里面,有一个指针,会指向进程地址空间,然后进程地址空间根据页表找到实际的物理内存。

然后CPU就会去找到这个进程,从而进行去调度

image-20231128170345068

而现在,我们知道当我们打开一个文件的时候,会创建出它的结构体struct file

image-20231128172612430

随后为了管理起来,task_struct中有一个指针,指向文件描述符表,在这个表中的下标对应着每一个文件。

image-20231128173022187

其中左侧的这一堆,我们都把他叫做,内核数据结构

image-20231128173215796

当我们再度创建一个文件的时候

image-20231128174328772

而我们上面进程替换替换是右边的部分,并不会对左边的部分有影响

image-20231128174423713

这里就是内存管理与文件管理的解耦

进程历史打开的文件与进行的各种重定向关系都和未来进行程序替换无关

程序替换,并不影响文件访问

3.代码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44
#define NONE       -1
#define IN_RDIR     0
#define OUT_RDIR    1
#define APPEND_RDIR 2

char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];
char* rdirfilename = NULL;
int rdir = NONE; 

const char* getusername()
{
    return getenv("USER");
}
const char* Gethostname()
{
    return getenv("HOSTNAME");
}
void getpwd()
{
    getcwd(pwd,sizeof(pwd));
}
void check_rdir(char* cmd)
{
    char* pos = cmd;
    while(*pos!='\0')
    {
        if(*pos == '>')
        {
            if(*(pos + 1) == '>')
            {
                *pos++ = '\0';
                *pos++ = '\0';
                while(isspace(*pos)) pos++;
                rdirfilename = pos;
                rdir=APPEND_RDIR;
                break;
            }
            else 
            {
                *pos = '\0';
                pos++;
                while(isspace(*pos)) pos++;
                rdirfilename = pos;
                rdir = OUT_RDIR;
                break;
            }
        }
        else if(*pos == '<')
        {
            *pos = '\0';  //ls -a -l < file.txt
            pos++;
            while(isspace(*pos)) pos++;
            rdirfilename = pos;
            rdir = IN_RDIR;
            break;
        }
        else 
        {
           // do nothing
        }
        pos++;
    }
}
void Interact(char* cline,int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),Gethostname(),pwd);
    char* s = fgets(cline,size,stdin);
    (void)s;
    assert(s);
    commandline[strlen(cline) - 1] = '\0';
    check_rdir(cline);
}
int splitstring(char cline[],char* _argv[])
{
    if(strcmp(cline,"") == 0) return 0;
    int i = 0;
    _argv[i++] = strtok(cline,DELIM);
    while(_argv[i++] = strtok(NULL,DELIM));
    return i - 1;
}

void NormalExcute(char* _argv[])
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return;
    }
    else if (id == 0)
    {
        int fd = 0;
        if(rdir == IN_RDIR)
        {
            fd = open(rdirfilename,O_RDONLY);
            dup2(fd,0);
        }
        else if(rdir == OUT_RDIR)
        {
            fd = open(rdirfilename,O_WRONLY|O_CREAT|O_TRUNC,0666);
            dup2(fd,1);
        }
        else if(rdir == APPEND_RDIR) 
        {
            fd = open(rdirfilename,O_WRONLY|O_CREAT|O_APPEND,0666);
            dup2(fd,1);
        }
        //子进程执行命令
        execvpe(_argv[0],_argv,environ);
        exit(EXIT_CODE);
    }
    else
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,0);
        if(rid == id)
        {
           last_code = WEXITSTATUS(status);
        }
    }
}

int BuildCommand(char* _argv[],int _argc)
{
    if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
    {
        chdir(_argv[1]);
        getpwd();
        sprintf(getenv("PWD"),"%s",pwd);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"export") == 0)
    {
        strcpy(myenv,_argv[1]);
        putenv(myenv);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)
    {
        if(strcmp(_argv[1],"$?") == 0)
        {
            printf("%d\n",last_code);
            last_code = 0;
        }
        else if(*_argv[1] == '$')
        {
            char* val = getenv(_argv[1] + 1);
            if(val) printf("%s\n",val);
        }
        else 
        {
            printf("%s\n",_argv[1]);
        }
        return 1;
    }


    if(_argc > 0 && strcmp(_argv[0],"ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}
int main()
{
    while(!quit)
    {
        rdirfilename = NULL;
        rdir = NONE;
        //2.交互问题,解决命令行
        Interact(commandline,sizeof(commandline));
        //3.子串分割问题,解析命令行
        int argc = splitstring(commandline,argv);
        if(argc == 0) continue;

        //4.指令的判断(内建命令和普通命令)
        int n = BuildCommand(argv,argc);
        //5.普通命令的执行
        if(!n) NormalExcute(argv);
    }
    return 0;
}

三、1号文件和2号文件的区别

当我们使用如下代码的时候

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#define filename "log.txt"

int main()
{
    fprintf(stdout,"hello normal message\n");
    fprintf(stdout,"hello normal message\n");
    fprintf(stdout,"hello normal message\n");
    fprintf(stdout,"hello normal message\n");
    fprintf(stdout,"hello normal message\n");
 
    fprintf(stderr,"hello error message\n");
    fprintf(stderr,"hello error message\n");
    fprintf(stderr,"hello error message\n");
    fprintf(stderr,"hello error message\n");
    fprintf(stderr,"hello error message\n");
    return 0;
}


运行结果为,我们可以看到,这两个文件都是往显示器上打印的,似乎没有什么区别

image-20231128175412685

但是如果我们这样做

image-20231128175558749

我们会发现一部分重定向了,一部分没有重定向

在一开始的时候是这样的

image-20231128180433678

随后发生了重定向,此时这个重定向仅仅是对于1号的文件的重定向

image-20231128180524995

这样的话,凡是原来往1里面写的,就会写进这个normal.log文件了

往2里面写的就往显示屏上打印了

如果我们在命令行中的是这样的

image-20231128180848142

其实上面是一个简写

下面是完整的,代表1重定向到normal.log,2重定向到err.log

./mytest 1>normal.log 2>err.log

这样的话就可以分开了

如果我们就想要重定向到一个文件中,那么可以这样做

./mytest 1>all.log 2>&1

image-20231128181522122

同理,前面的这个1也是可以省略的

image-20231128181631739

这个命令中

2>&1的意思是把1号文件描述符里面的内容写到2号文件描述符中。

这个的前提是已经把前半部分指令的操作做完了。而1已经指向这个这个all了。

所以最终1和2都指向这个文件了。所以就全部写入到一个文件中了

四、如何理解“一切皆文件?”

我们知道系统中有很多设备,如下图所示

这些外设,几乎都要去提供他们的读写方法,只不过对于键盘而言,写方法为空,对于显示器而言,读方法为空而已。但是他们都有读写方法

image-20231128222532291

他们都可以用同一种结构体来表示。然后用类似的读写接口

image-20231128223058653

对于这些硬件,我们都可以以文件的方式用open打开。然后创建对应的struct file内核数据结构。

image-20231128223741964

然后由于我们要对这些设备进行读写

所以linux内核提供了另外一个方法表的数据结构

struct operation_func()
{
	 int (*wirtep)();
	 int (*readp)();
}

当我们想要访问某个设备的时候。会创建这个方法表的数据结构,然后提供一个指针指向这个方法表,方法表中的函数指针就指向对应的接口

image-20231128224429863

如此一来,task_struct就会通过文件描述符表中的数组从而去调用对应的方法数据结构,最终达到调用read函数

这样就可以实现上层都是调用一样的函数了,而根据下层的不同动态的调用不同的方法了

image-20231128225646625

所以所谓的一切皆文件,就是相当于在文件这一层封装了一个文件对象,让文件对象中的指针指向不同设备的函数方法,然后通过上层的一个方法数据结构,来对这些进行汇总。使得我们压根就不需要关心下层是如何的。只需要知道,以后要读取这个文件就调用这个方法即可

image-20231128230206588

所以从文件对象这一层,往上就有了一切皆文件,这一层我们也叫做,VFS(虚拟文件系统)

image-20231128230332248

而我们似乎就可以发现,这不就是C++中的多态吗?

上层都是基类,下层就是派生类。

所以面向对象就是历史的必然!

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

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

相关文章

GPT实战系列-大模型训练和预测,如何加速、降低显存

GPT实战系列-大模型训练和预测&#xff0c;如何加速、降低显存 不做特别处理&#xff0c;深度学习默认参数精度为浮点32位精度&#xff08;FP32&#xff09;。大模型参数庞大&#xff0c;10-1000B级别&#xff0c;如果不注意优化&#xff0c;既耗费大量的显卡资源&#xff0c;…

数字图像处理(实践篇) 十六 基于分水岭算法的图像分割

目录 一 分水岭算法 二 利用OpenCV实现分水岭算法的过程 三 实践 一 分水岭算法 基于任何灰度图像都可以视为地形表面&#xff0c;其中高强度表示山峰和山丘&#xff0c;而低强度表示山谷。首先&#xff0c;开始用不同颜色的水&#xff08;标签&#xff09;填充每个孤立的山…

【虚拟机】Docker基础 【二】

2.2.数据卷 容器是隔离环境&#xff0c;容器内程序的文件、配置、运行时产生的容器都在容器内部&#xff0c;我们要读写容器内的文件非常不方便。大家思考几个问题&#xff1a; 如果要升级MySQL版本&#xff0c;需要销毁旧容器&#xff0c;那么数据岂不是跟着被销毁了&#x…

Python常用库大全及简要说明,附官方网站链接地址

文章目录 前言环境管理包管理包仓库分发构建工具交互式解析器文件日期和时间文本处理特殊文本格式处理自然语言处理文档配置命令行工具下载器图像处理OCR音频Video地理位置HTTP数据库数据库驱动ORMWeb 框架权限CMS电子商务RESTful API验证模板引擎队列搜索动态消息资源管理缓存…

【数据库】数据库并发控制的目标,可串行化序列的分析,并发控制调度器模型

数据库并发控制 ​专栏内容&#xff1a; 手写数据库toadb 本专栏主要介绍如何从零开发&#xff0c;开发的步骤&#xff0c;以及开发过程中的涉及的原理&#xff0c;遇到的问题等&#xff0c;让大家能跟上并且可以一起开发&#xff0c;让每个需要的人成为参与者。 本专栏会定期更…

接口测试Postman 变量

Postman变量有以下几种类型&#xff1a; 1、环境变量&#xff08;Environment Variables&#xff09;: 环境变量是在Postman的环境中定义的全局变量&#xff0c;可在不同请求之间共享。通过设置不同环境&#xff0c;可以轻松切换不同的配置&#xff08;如开发环境、测试环境…

[FUNC]判断窗口在哪一个屏幕上

#Requires AutoHotkey v2.0#z:: { ToolTip "Notepad窗口所在显示屏是&#xff1a;" GetMonitor() } GetMonitor() {CoordMode("Mouse", "Screen"); MouseGetPos &mx, &myWinGetPos &mx, &my,,,"ahk_class Notepad"…

CentOS7根分区扩容之一

Centos默认根分区50G&#xff0c;很快接近100%&#xff0c;如果你的系统使用了全部磁盘&#xff0c;文件系统是xfs&#xff0c;根分区和/home都是逻辑卷&#xff0c;那么在没有额外的磁盘增加情况下&#xff0c;可以从/home卷中切分一部分空间增加到根分区空间。 1.由于xfs格式…

【参数估计】---点估计之矩估计

点估计之矩估计 &#x1f47b;什么是参数估计&#x1f47b;引例---理解参数估计&#x1f41f;点估计&#x1f36d;引例&#x1f36d;点估计问题 &#x1f41f;矩估计&#x1f36d;预备知识&#x1f36d;矩估计的求解步骤&#x1f36d;矩估计例题 &#x1f47b;什么是参数估计 在…

kkFileView 从源码编译最新安装包

目录 一、前言二、拉取 kkFileView 最新代码三、kkFileView 打包 一、前言 kkFileView 是一个开源的附件在线预览项目&#xff0c;可以让你的项目方便的在线预览附件&#xff0c;包括比如&#xff1a;doc、docx、pdf、xml、xls、xlsx、ppt、pptx、zip、png、jpg、txt、mp4等常…

Mybatis相关API(Sqlsession和sqlsessionFactroy)

代码 private static SqlSessionFactory sqlSessionFactory;static { ​try { // 获得核心配置文件String resource "mybits-config.xml"; // 加载核心配置文件InputStream inputStream Resources.getResourceAsStream(resource…

WebUI自动化学习(Selenium+Python+Pytest框架)005

基础知识学习完毕&#xff0c;接下来我们开始学习测试框架啦&#xff01;&#xff01;&#xff01; 首先来回顾一下python自带的Unittest框架&#xff1a; Python基础学习016__UnitTest-CSDN博客文章浏览阅读97次。Testcase:测试用例:这个测试用例是UnitTest的组成部分,不是手…

前端面试高频考点—TCP vs UDP

目录 简介&#xff1a; 区别&#xff1a; 应用选择&#xff1a; tcp为什么需要三次握手&#xff1f; 简介&#xff1a; TCP(传输控制协议)和UDP&#xff08;用户数据报协议&#xff09; TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议&#xff0c;是专门为了在不…

AES加密技术:原理与应用

一、引言 随着信息技术的飞速发展&#xff0c;数据安全已成为越来越受到重视的领域。加密技术作为保障数据安全的重要手段&#xff0c;在信息安全领域发挥着举足轻重的作用。AES&#xff08;Advanced Encryption Standard&#xff09;作为一种对称加密算法&#xff0c;自1990年…

Redis部署-主从模式

目录 单点问题 主从模式 解析主从模式 配置redis主从模式 info replication命令查看复制相关的状态 断开复制关系 安全性 只读 传输延迟 拓扑结构 数据同步psync replicationid offset psync运行流程 全量复制流程 无硬盘模式 部分复制流程 积压缓冲区 实时复…

CCC数字车钥匙(八)——BLE配对相关字段

2.1 配对连接协议 2.1.3 所有者配对广播 对于所有者配对&#xff0c;仅支持Legacy LE 1M PHY。ADV_IND需要按照Section 2.3.1.1 Volume 6 Part B。 事件类型&#xff1a;无指向可连接和可扫描。 ADV_IND中包含广播地址和广播数据&#xff0c;如下所示&#xff0c;其中广播地址…

iris+vue上传到本地存储【go/iris】

iris部分 //main.go package mainimport ("fmt""io""net/http""os" )//上传视频文件部分 func uploadHandler_video(w http.ResponseWriter, r *http.Request) {// 解析上传的文件err : r.ParseMultipartForm(10 << 20) // 设置…

多线程(初阶五:wait和notify)

目录 一、概念 二、用法 &#xff08;1&#xff09;举个栗子&#xff1a; &#xff08;2&#xff09;wait和notify的使用 1、没有上锁的wait 2、当一个线程被wait&#xff0c;但没有其他线程notify来释放这个wait 3、两个线程&#xff0c;有一个线程wait&#xff0c;有一…

目标检测——Mask R-CNN算法解读

论文&#xff1a;Mask R-CNN 作者&#xff1a;Kaiming He Georgia Gkioxari Piotr Dollar Ross Girshick 链接&#xff1a;https://arxiv.org/abs/1703.06870 代码&#xff1a;https://github.com/facebookresearch/Detectron R-CNN系列其他文章&#xff1a; R-CNN算法解读SPP…

Leecode 【一】

环形链表: 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&…