BIT.4 Linux进程控制

news2024/11/17 21:25:32

目录

  • 进程创建
    • fork函数初识
    • 写实拷贝
    • fork常规用法
    • fork调用失败的原因
    • 补充知识
  • 进程终止
    • 进程退出场景
    • 进程常见退出方法
    • exit函数与_exit函数
    • return 退出
    • 补充知识
  • 进程等待
    • 进程等待必要性
    • 进程等待的方法
      • wait方法
      • waitpid方法
      • wait / waitpid 阻塞代码
      • WIFEXITED
      • wait / waitpid 非阻塞代码
    • 获取子进程status
  • 进程程序替换
    • 替换原理
    • 替换函数
    • 函数解释
    • 命名理解
    • makefile 生成多个文件
    • 简易的shell
  • 函数和进程之间的相似性

进程创建

fork函数初识

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

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

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度
    在这里插入图片描述
    当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以
    开始它们自己的旅程,看如下程序。
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6   printf("我是父进程\n");
  7   
  8   pid_t id = fork(); // 调用fork函数创建子进程                                
  9   if(id < 0)
 10   {
 11     printf("创建子进程失败\n");
 12     return 1;
 13   }
 14   else if(id == 0)
 15   {
 16     while(1)
 17     {
 18       printf("我是子进程: pid: %d, ppid: %d\n", getpid(), getppid());
 19       sleep(1);
 20     }
 21   }                 
 22   else                 
 23   {                                     
 24     while(1)           
 25     {
 26       printf("我是父进程: pid: %d, ppid: %d\n", getpid(), getppid());
 27       sleep(1);                                                  
 28     }                                                     
 29   }                                   
 30   return 0;                                           
 31 }     
 // 运行结果
 我是父进程
我是父进程: pid: 26264, ppid: 22610
我是子进程: pid: 26265, ppid: 26264
我是父进程: pid: 26264, ppid: 22610
我是子进程: pid: 26265, ppid: 26264
我是父进程: pid: 26264, ppid: 22610
.........
.........  

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

写实拷贝

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副
本。具体见下图
在这里插入图片描述

fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数

fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

补充知识

在这里插入图片描述

进程终止

进程退出场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):(注意echo $?是获取最近一个程序执行完毕的退出码)

  1. 从main返回
  2. 调用exit
  3. _exit
    异常退出:
  • ctrl + c,信号终止

exit函数与_exit函数

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<stdlib.h>
  5 int main()
  6 {
  7   printf("你可以看到这里的打印信息码?"); // 注意这里没有换行
  8   sleep(3);
  9   exit(13);                                                                  
 10 }
  • 上述exit调用后的现象
    在这里插入图片描述
  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<stdlib.h>
  5 int main()
  6 {
  7   printf("你可以看到这里的打印信息码?");
  8   sleep(3);
  9   _exit(13);                                                                  
 10 }
  • 上述_exit调用后的现象
    在这里插入图片描述

在这里插入图片描述
exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit

return 退出

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返
回值当做 exit的参数。

补充知识

查看退出码对应的错误信息

 12 int main()                                                                    
 13 {
 14   int i = 0;
 15   for(i = 0; i < 100; i++) // i < 100 是打印出100条,这里的数值可以增加
 16   {
 17     printf("%d: %s\n",i, strerror(i));
 18   }
 19   return 0;
 20 }

// 结果
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
.....
.....

在这里插入图片描述

进程等待

进程等待必要性

  • 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

进程等待的方法

wait方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
	成功返回被等待进程pid,失败返回-1。
参数:
	输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
	当正常返回的时候waitpid返回收集到的子进程的进程ID;
	如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
	如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
	pid:
		Pid=-1,等待任一个子进程。与wait等效。
		Pid>0.等待其进程ID与pid相等的子进程。
	status:
		WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
		WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
	options:
		WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

wait / waitpid 阻塞代码

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

int code = 0;

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        exit(1); //标识进程运行完毕,结果不正确
    }
    else if(id == 0)
    {
        //子进程
        int cnt = 5;
        while(cnt)
        {
            printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(15);
    }
    else
    {
        //父进程
        printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
        // pid_t ret = wait(NULL); //阻塞式的等待!
        // pid_t ret = waitpid(id, NULL, 0); //阻塞式的等待! ==  wait(NULL)  option默认为0, 表示阻塞等待
        int status = 0;
        pid_t ret = waitpid(id, &status, 0); //阻塞式的等待!
        if(ret > 0)
        {
            // 0x7F -> 0000.000 111 1111
            printf("等待子进程成功, ret: %d, 子进程收到的信号编号: %d,子进程退出码: %d\n",\
                    ret, status & 0x7F ,(status >> 8)&0xFF); //0xff --> 0000...000 1111 1111
            printf("code: %d\n", code);
        }
        // 阻塞式的等待,父进程不会执行下面代码,会阻塞等待子进程,当子进程结束,获取wait返回的结果
        // 再执行下面的代码
        while(1)
        {
            printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
            sleep(1);
        }
    }
}

WIFEXITED

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork()");
        exit(1);
    }
    else if (id == 0)
    {
        // 子进程
        printf("我是子进程: PID %d, PPID %d\n", getpid(), getppid());
    }
    else
    {
        // 父进程
        int status = 0;
        // 只有子进程退出的时候,父进程才会waitpid函数,进行返回!![父进程依旧还活着呢!!]
        // waitpid/wait 可以在目前的情况下,让进程退出具有一定的顺序性!
        // 将来可以让父进程进行更多的收尾工作.
        // id > 0 : 等待指定进程
        // id == -1: 等待任意一个子进程退出, 等价于wait();
        // options: 默认为0,代表阻塞等待, WNOHANG: 叫做非阻塞等待
        pid_t result = waitpid(id, &status, 0); // 默认是在阻塞状态去等待子进程状态变化(就是退出!)
        if (result > 0)
        {
            // 可以不这么检测
            //printf("父进程等待成功, 退出码: %d, 退出信号: %d\n", (status>>8)&0xFF, status & 0x7F);
            if (WIFEXITED(status)) // WIFEXITED(status): 若为正常终止子进程返回的状态,则为真
            {
                //子进程是正常退出的
                // 若WIFEXITED非零,提取子进程退出码
                printf("子进程执行完毕,子进程的退出码: %d\n", WEXITSTATUS(status));
            }
            else
            {
                // 子进程是不是正常退出的
                printf("子进程异常退出: %d\n", WIFEXITED(status));
            }
        }
    }
}

wait / waitpid 非阻塞代码


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

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

std::vector<handler_t> handlers; //函数指针数组

void fun_one()
{
    printf("这是一个临时任务1\n");
}
void fun_two()
{
    printf("这是一个临时任务2\n");
}

// 设置对应的方法回调
// 以后想让父进程闲了执行任何方法的时候,只要向Load里面注册,就可以让父进程执行对应的方法喽!
void Load()
{
    handlers.push_back(fun_one);
    handlers.push_back(fun_two);
}

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // 子进程
        int cnt = 5;
        while (cnt)
        {
            printf("我是子进程: %d\n", cnt--);
            sleep(1);
        }

        exit(11); // 11 仅仅用来测试
    }
    else
    {
        int quit = 0;
        while (!quit)
        {
            int status = 0;
            pid_t res = waitpid(-1, &status, WNOHANG); //以非阻塞方式等待
            if (res > 0)
            {
                //等待成功 && 子进程退出
                printf("等待子进程退出成功, 退出码: %d\n", WEXITSTATUS(status));
                quit = 1;
            }
            else if (res == 0)
            {
                //等待成功 && 但子进程并未退出
                printf("子进程还在运行中,暂时还没有退出,父进程可以在等一等, 处理一下其他事情??\n");
                if (handlers.empty()) Load();
                for (auto iter : handlers)
                {
                    //执行处理其他任务
                    iter();
                }
            }
            else
            {
                //等待失败
                printf("wait失败!\n");
                quit = 1;
            }
            sleep(1);
        }
    }
}

在这里插入图片描述

获取子进程status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

在这里插入图片描述

在这里插入图片描述

进程程序替换

替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

(exce函数是程序替换,调用该程序成功之后,回将当前程序的所有代码和数据进行替换,包括执行的和没有执行的,所以一旦调用成功,后续所有代码去不不会执行!)
(程序替换是通过特定的接口,加载磁盘上的一个权限的程序(代码和数据),加载到调用进程的地址空间)
(进程替换,没有创建新的子进程,就是将磁盘的程序加载到内存,并和当前进程的页表重新建立映射)
在这里插入图片描述

替换函数

其实有六种以exec开头的函数,统称exec函数:

#include <unistd.h>`
// const char *path是为了找到程序! path: 路径 + 目标文件名
//  【 const char *arg, ... 】  可变参数列表, 可以传入多个不定个数的参数
// 注意最后一个参数, 必须为  NULL ,标识参数传递完毕!
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 execve(const char *path, char *const argv[], char *const envp[]);

函数解释

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。

命名理解

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

在这里插入图片描述

int main()
{
    printf("当前进程的开始代码!\n");

    //execl("/usr/bin/ls", "ls", NULL);
    execl("/usr/bin/ls", "ls", "--color=auto","-l", NULL);
    // exce函数失败直接退出
    exit(1); 
    
    //execl("/usr/bin/ls", "ls", "-l", "-a", "-i", NULL);
    //execl("/usr/bin/ls", "ls", "-l", "-a", "-i", NULL);
    //execl("/usr/bin/top", "top", NULL);

    printf("当前进程的结束代码!\n");
    return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// ./mycmd -a/-b/-c...
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        printf("can not execute!\n");
        exit(1);
    }

    //MY_105_VAL: 目前并不存在!
    printf("获取环境变量: MY_105_VAL: %s\n", getenv("MY_105_VAL"));

    if(strcmp(argv[1], "-a") == 0)
    {
        printf("hello a!\n");
    }
    else if(strcmp(argv[1], "-b") == 0)
    {
        printf("hello b!\n");
    }
    else{
        printf("default!\n");
    }

    return 0;
}
/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define NUM 16

//const char *myfile = "/home/whb/105/online-code---phase-105/lesson17/mycmd"; // mycmd是上面代码生成的二进制文件
const char *myfile = "./mycmd"; // 相对路径

int main(int argc, char*argv[], char *env[])
{
    // 为什么我要创建子进程?
    // 为了不影响父进程
    // 我们想让父进程聚焦在读取数据,解析数据,指派进程执行代码的功能!
    // 如果不创建,那么我们替换的进程只能是父进程,如果创建了,替换的进程就是子进程,而不影响父进程(为什么?)
    //while(1)
    //{
        //1. 显示一个提示行: root@localhost# 
        //2. 获取用户输入的字符串,fgets,scanf。 -> ls -a -l
        //3. 对字符串进行解析
        //4. 下面的代码
       char *const _env[NUM] = {
           (char *)"MY_105_VAL=888777666555",
           NULL
       };
        pid_t id = fork();
        if(id == 0)
        {
            //子进程
            //ls -a -l
            printf("子进程开始运行, pid: %d\n", getpid());
            sleep(3);
            char *const _argv[NUM] = {
                (char*)"ls",
                (char*)"-a",
                (char*)"-l",
                (char*)"-i",
                NULL
            };
            // 自己写的环境变量_env, 会将它传给 mycmd, 里面可以获取到,从这里我们可以想到,
            // 环境变量具有全局属性的本质是就是通过execle, 将环境变量传给子进程
            execle(myfile, "mycmd", "-a", NULL, _env);
            //execle(myfile, "mycmd", "-a", NULL, env);
            //execle(myfile, "mycmd", "-a", NULL); // 也可以运行
          
            //execlp("./test.py", "test.py", NULL);
            //execlp("bash", "bash", "test.sh", NULL);
            //execlp("python", "python", "test.py", NULL);
            //execl(myfile, "mycmd", "-b", NULL);
            //execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
            //execv("/usr/bin/ls", _argv); //和上面的execl只有传参方式的区别
            //execlp("ls", "ls", "-a", "-l", NULL);
            //execvp("ls", _argv);
            exit(1);
        }
        else 
        {
            //父进程
            printf("父进程开始运行, pid: %d\n", getpid());
            int status = 0;
            pid_t id = waitpid(-1, &status, 0); //阻塞等待, 一定是子进程先运行完毕,然后父进程获取之后,才退出!
            if(id > 0)
            {
                printf("wait success, exit code: %d\n", WEXITSTATUS(status));
            }
        }
    //} //end while(1)
    return 0;
}

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在
man手册第3节。这些函数之间的关系如下图所示。

下图exec函数族 一个完整的例子:
在这里插入图片描述

makefile 生成多个文件

在这里插入图片描述

简易的shell

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

#define NUM 1024
#define SIZE 32
#define SEP " "

//保存完整的命令行字符串
char cmd_line[NUM];
//保存打散之后的命令行字符串
char *g_argv[SIZE];

// shell 运行原理 : 通过让子进程执行命令,父进程等待&&解析命令
int main()
{
    //0. 命令行解释器,一定是一个常驻内存的进程,不退出
    while(1)
    {
        //1. 打印出提示信息 [whb@localhost myshell]# 
        printf("[root@localhost myshell]# ");
        fflush(stdout);
        memset(cmd_line, '\0', sizeof cmd_line);
        //2. 获取用户的键盘输入[输入的是各种指令和选项: "ls -a -l -i"]
        if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
        {
            continue;
        }
        cmd_line[strlen(cmd_line)-1] = '\0';
        //"ls -a -l -i\n\0"
        //printf("echo: %s\n", cmd_line);
        //3. 命令行字符串解析:"ls -a -l -i" -> "ls" "-a" "-i"
        g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串
        int index = 1;
        if(strcmp(g_argv[0], "ls") == 0)
        {
            g_argv[index++] = "--color=auto";
        }
        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

        //for debug
        //for(index = 0; g_argv[index]; index++)
        //    printf("g_argv[%d]: %s\n", index, g_argv[index]);
        //4. TODO,内置命令, 让父进程(shell)自己执行的命令,我们叫做内置命令,内建命令
        //内建命令本质其实就是shell中的一个函数调用
        if(strcmp(g_argv[0], "cd") == 0) //not child execute, father execute
        {
            if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd ..

            continue;
        }
        //5. fork()
        pid_t id = fork();
        if(id == 0) //child
        {
            printf("下面功能让子进程进行的\n");
            //cd cmd , current child path
            execvp(g_argv[0], g_argv); // ls -a -l -i
            exit(1);
        }
        //father
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));
    }
}

函数和进程之间的相似性

exec/exit就像call/return
一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的
操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。
这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程
序之内的模式扩展到程序之间。如下图
在这里插入图片描述
一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来
返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。

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

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

相关文章

LeetCode刷题复盘笔记—一文搞懂动态规划之718. 最长重复子数组问题(动态规划系列第三十一篇)

今日主要总结一下动态规划的一道题目&#xff0c;718. 最长重复子数组 题目&#xff1a;718. 最长重复子数组 Leetcode题目地址 题目描述&#xff1a; 给两个整数数组 nums1 和 nums2 &#xff0c;返回 两个数组中 公共的 、长度最长的子数组的长度 。 示例 1&#xff1a; …

华熙LIVE·五棵松商业北区明年国庆亮相 互动体验升级

京城着名的活力聚集地——华熙LIVE五棵松明年将增添两万多平米商业区&#xff0c;新增商业区位于现有商业区北侧并与之相连通&#xff0c;业态在承袭现有沉浸式互动体验业态基础上&#xff0c;将引进元宇宙等前沿科技和跳楼机等娱乐设施&#xff0c;使互动体验进一步升级。项目…

一文搞懂Linux内核中断机制原理与实现

为什么需要中断&#xff1f; 如果让内核定期对设备进行轮询&#xff0c;以便处理设备&#xff0c;那会做很多无用功&#xff0c;因为外设的处理速度一般慢于CPU&#xff0c;而CPU不能一直等待外部事件。所以能让设备在需要内核时主动通知内核&#xff0c;会是一个聪明的方式&a…

JWT渗透与攻防(一)

目录 前言 JWT漏洞介绍 案列演示之Leaky_JWT JWT漏洞具体的实现方式&#xff1a; 案列演示之JWT None Algorithm JWT漏洞工具的利用 JWT利用工具介绍 jwt_tool 漏洞利用 jwt-cracker c-jwt-cracker 前言 Json web token (JWT)相关漏洞对于渗透测试人员而言可能是一种…

node.js+uni计算机毕设项目店内点餐微信小程序LW(程序+小程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等…

【Pandas入门教程】如何从现有列派生新列

如何从现有列派生新列 来源&#xff1a;Pandas官网&#xff1a;https://pandas.pydata.org/docs/getting_started/intro_tutorials/index.html 笔记托管&#xff1a;https://gitee.com/DingJiaxiong/machine-learning-study 文章目录如何从现有列派生新列导包数据集准备【1】如…

C++——STL之stack和queue详解

C——STL之stack和queue详解&#x1f3d0;什么是stack和queue&#x1f3d0;stack和queue的实现&#x1f3c0;什么是deque&#x1f3c0;stack的模拟实现&#x1f3c0;queue的模拟实现&#x1f3d0;优先级队列&#xff08;priority_queue)&#x1f3c0;优先级队列的实现⚽push⚽p…

Spring Authorization Server1.0 介绍与使用

一、版本使用 1、Java&#xff1a;17或者更高的版本。 2、springboot 3.0 3、Spring Authorization Server 1.0版本。 <dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-authorization-server</ar…

使用proxy_pool来为爬虫程序自动更换代理IP

文章目录1. 前言2. 教程3. 官网4. 在线demo4.1. 本地部署4.2. 安装4.2.1. Python源码构建安装4.2.1.1. 安装redis数据库4.2.1.1.1. 下载redis源码4.2.1.1.2. 启动redis服务4.2.1.1.3. 安装redis服务4.2.1.1.4. 再次通过命令启动redis服务4.2.1.1.5. 测试redis服务是否可用4.2.1…

node.js+uni计算机毕设项目基于微信小程序的车位共享系统LWPPT(程序+小程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等…

C语言程序设计--银行管理系统

主界面 登入界面 #include <stdio.h> #include <malloc.h> #include <conio.h> #include <time.h> #include <windows.h> #define MB_ICONINFORMATION MB_ICONASTERISK //对 错误 struct account_information /…

腾讯云轻量应用服务器使用Matomo 应用镜像搭建网站流量统计系统!

Matomo 是一款开源的网站数据统计软件&#xff0c;可以用于跟踪、分析您的网站的流量&#xff0c;同时充分保障数据安全性、隐私性。该镜像基于 CentOS 7.6 64位操作系统&#xff0c;已预置 Nginx、MariaDB、PHP 软件。本文介绍如何使用 Matomo 快速搭建您的网站流量统计系统。…

【文本检测】2、DBNet++ | 为 DBNet 引入多级特征图聚合模块 ASF

文章目录一、背景二、方法2.1 Adaptive Scale Fusion (ASF) 模块2.2 Binarization2.3 Adaptive Threshold2.4 Deformable Convolution2.5 Label Generation2.6 Optimization三、效果论文&#xff1a;Real-Time Scene Text Detection with Differentiable Binarization and Adap…

Hadoop综合项目——二手房统计分析(可视化篇)

Hadoop综合项目——二手房统计分析&#xff08;可视化篇&#xff09; 文章目录Hadoop综合项目——二手房统计分析&#xff08;可视化篇&#xff09;0、 写在前面1、数据可视化1.1 二手房四大一线城市总价Top51.2 统计各个楼龄段的二手房比例1.3 统计各个城市二手房标签的各类比…

人工智能轨道交通行业周刊-第27期(2022.12.12-12.25)

本期关键词&#xff1a;虚拟中台、智轨、数字员工客服、钢轨光带异常、小目标检测 1 整理涉及公众号名单 1.1 行业类 RT轨道交通中关村轨道交通产业服务平台人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨…

4、前端笔记-JS-数据类型

1、数据类型简介 1.1为什么需要数据类型 不同的数据占用的存储空间不同&#xff0c;为了充分利用存储空间&#xff0c;便于把数据分成所需内存大小不同的数据&#xff0c;定义了不同的数据类型 1.2变量的数据类型 js是弱类型&#xff08;动态语言&#xff09;的语言&#x…

这12类Oracle日期函数,全都给你总结了

在使用Oracle数据库过程中&#xff0c;对日期的使用不可避免&#xff0c;那Oracle中的日期函数有哪些呢&#xff1f;本篇就日期函数进行整理了&#xff0c;不一定全部了解记住&#xff0c;但是要做到心中有数&#xff0c;以后在写脚本的时候就不会绕弯子了。 1、sysdate、curr…

大话设计模型 Task05 状态、适配、单例

目录一、状态模式问题描述问题分析模式定义代码实现二、适配器模式问题描述问题分析模式定义代码实现三、单例模式问题描述问题分析模式定义代码实现一、状态模式 问题描述 假设我们要描述一名员工一天不同时间的工作状态&#xff0c;正常来看是比较简单的&#xff0c;直接从…

卡塔尔世界杯半自动越位识别技术(SAOT)的工作原理

随着卡塔尔世界杯的深入举行&#xff0c;半自动越位识别技术 (Semi-automated offside technology&#xff0c;简称为 SAOT) 这项数字技术正在被越来越多的国内外球迷所熟知。 作为 VAR(Video Assistant Referee&#xff0c;视频助理裁判) 的扩展&#xff0c;SAOT 的引入是为了…

腾讯云轻量应用服务器使用 OpenFaaS 部署云函数!

OpenFaaS 是开源的流行 FaaS&#xff08;Function-as-a-Service&#xff0c;函数即服务&#xff09;框架&#xff0c;OpenFaaS 让开发者聚焦业务代码的编写&#xff0c;无需过多关注语言框架、部署、配置等其他步骤。 轻量应用服务器 Lighthouse 为您提供了 OpenFaaS 应用镜像…