C语言进程的相关操作

news2024/12/23 14:27:31

C语言进程的相关操作

进程简介

  • 每个进程都有一个非负整数形式到的唯一编号,即PID(Process Identification,进程标识)
  • PID在任何时刻都是唯一的,但是可以重用,当进程终止并被回收以后,其PID就可以为其他进程使用
  • 进程的PID由系统内核根据延迟重用算法生成,以确保新进程的PID不同于最近终止进程到的PID
  • 其中0号进程,叫做交换进程,系统内核中的一部分,所有进程的根进程,磁盘上没有它的可执行文件
  • 1号进程是init进程,在系统自举过程结束时由调度进程创建,读写与系统相关的初始化文件,引导系统至一个特定状态,以超级用户特权运行的普通进程,永不终止
  • 除去调度进程以外,系统中的每个进程都有一个唯一的父进程,对任何一个子进程而言,其父进程的PID即是它的PPID
  • 下面这些函数都包含在unistd.h头文件中
  • pid_t getpid(void);返回调用进程的PID
  • pid_t getppid(void);返回调用进程的父进程的PID
  • uid_t getuid(void);返回调用进程的实际用户ID
  • gid_t getgid(void);返回调用进程的实际组ID
  • uid_t geteuid(void);返回调用进程的有效用户ID
  • gid_t getegid(void);返回调用进程的有效组ID

创建子进程

  • 创建子进程的函数包含在unistd.h头文件中

  • fork函数

    • pid_t fork(void);
      • 功能:创建调用进程的子进程
      • 返回值:失败返回-1,成功情况下返回的变量在父进程中是PID,在子进程中是0
      • 可以通过这个返回值来执行父进程和子进程
      • 当系统中的总的线程数达到了上限,或者用户的总进程达到了上限,fork函数会失败。
  • 创建子进程示例代码

    #include <stdio.h>
    #include <unistd.h>
    
    int main(void)
    {
        printf("haha\n");
        // 创建子进程
        int pid = fork();
        printf("heihei\n");
        return 0;
    }
    
    /*
    haha
    heihei
    heihei
    */
    

父子进程间的关系

  • 以下是父子进程中数据相关copy的示例图
    在这里插入图片描述

  • 验证上图

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <time.h>
    
    
    int global = 100;  // 父进程全局变量->数据区
    
    int main(void)
    {
    int local = 200;  // 父进程局部变量->栈区
    int *heap = malloc(sizeof(int));  // 动态分配内存->堆区
    *heap = 3;
    
    printf("父进程第一次打印: PID->%d %p->%d %p->%d %p->%d\n", getpid(), &global, global, &local, local, heap, *heap);
    // 创建子进程
    pid_t pid = fork();
    if(pid == 0)
    {
    // 子进程操作,数据会从父进程copy一份过来,这里执行++操作
    printf("子进程打印: PID->%d PPID->%d %p->%d %p->%d %p->%d\n", getpid(), getppid(), &global, ++global, &local, ++local, heap, ++*heap);
    return 0;
    }
    sleep(1);  // 这里等1s,让子进程++
    printf("父进程第二次打印: PID->%d %p->%d %p->%d %p->%d\n", getpid(), &global, global, &local, local, heap, *heap);
    
    return 0;
    }
    
    /*
    父进程第一次打印: PID->1674604 0x5577e1acc010->100 0x7ffd4d4bfaa8->200 0x5577e23422a0->3
    子进程打印: PID->1674605 PPID->1674604 0x5577e1acc010->101 0x7ffd4d4bfaa8->201 0x5577e23422a0->4
    父进程第二次打印: PID->1674604 0x5577e1acc010->100 0x7ffd4d4bfaa8->200 0x5577e23422a0->3
    
    这里的父进程和子进程地址一样是虚拟地址里面一样,因为每个进程都有一个独立的虚拟地址池,相互不影响的
    发现子进程跟父进程互相不影响,验证了上图的案例
    */
    
  • 父子进程操作文件,其实是共享一个文件表项的
    在这里插入图片描述

  • 验证上图

    #include <stdio.h>
    #include <string.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <time.h>
    
    int main(void)
    {
        // 父进程打开文件
        int fd = open("./test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
        if(fd == -1)
        {
            perror("open");
            return -1;
        }
    
        // 父进程写入数据
        char *data = "hello bhlu!";
        if(write(fd, data, strlen(data)) == -1)
        {
            perror("write");
            return -1;
        }
    
        // 创建子进程
        pid_t pid = fork();
        if(pid == 0)
        {
            // 子进程修改文件读写位置
            if(lseek(fd, -5, SEEK_END) == -1)
            {
                perror("lseek");
                return -1;
            }
            return 0;
        }
    
        // 再次插入数据,验证子进程修改的读写位置是否生效
        sleep(1);  // 先等1s,让子进程执行完
        data = "linux\n";
        if(write(fd, data, strlen(data)) == -1)
        {
            perror("write");
            return -1;
        }
    
        // 关闭文件
        close(fd);
        return 0;
    }
    
    /*
    cat test.txt
    hello linux
    发现是修改成功的,说明上图是对的,子进程和父进程共用一个文件表项
    */
    

进程的终止

以下内容只是简单的介绍进程的终止,以便理解

  • 进程的终止分为两种

    1. 正常终止:分为三种情况

      1. main函数中正常返回

      2. 使用exit函数终止:exit函数可以在任何函数中执行令进程结束,return语句只有在main函数中执行才能令进程结束

        #include <stdlib.h>
        
        void exit(int status);
        /* 
        功能: 令进程终止
        参数: status 进程的退出码,相当于main函数的返回值
        无返回值
        */
        
        /*
        exit函数在终止前会做以下几件收尾工作
        1. 调用实现通过atexit或on_exit函数注册的函数退出函数
        2. 冲刷并关闭所有仍处于打开状态的标准I/O流
        3. 删除所有通过tmpfile函数创建的临时文件
        4. 执行_exit(status);
        使用exit函数令进程终止,通常使用EXIT_SUCCESS和EXIT_FAILUR两个宏
        EXIT_SUCCESS -> 1; EXIT_FAILUR -> 0;
        */
        
      3. 调用_exit/_Exit函数令进程终止

        // _exit函数
        #include <unistd.h>
        
        void _exit(int status);
        /*
        参数: status 进程的退出码,相当于main函数的返回值
        无返回值
        */
        
        // _Exit函数
        #include <stdlib.h>
        
        void _Exit(int status);
        /*
        参数: status 进程的退出码,相当于main函数的返回值
        无返回值
        */
        
        /*
        _exit函数在终止前会做以下几件收尾工作
        1. 关闭所有仍处于打开状态的文件描述符
        2. 将调用进程的所有子进程托付过init进程
        3. 向调用进程的父进程发送SIGCHLD(7)信号
        4. 令调用进程终止运行,将status的低八位作为退出码保存在其终止状态中
        */
        
    2. 异常终止

      1. 进程执行了系统认为具有危险性的操作时,或者系统本身发生故障或意外,内核会向进程发送特定的信号

        SIGILL(4) -> 进程试图执行非法指令
        SIGBUS(7) -> 硬件或对齐错误
        SIGEPE(8) -> 浮点异常
        SIGSEGV(11) -> 无效内存访问
        SIGPWR(30) -> 系统供电不足
        
      2. 人为触发信号

        SIGINT(2) -> Ctrl+c
        SIGQUIT(3) -> Ctrl+\
        SIGKILL(9) -> 不能被捕获或忽略的进程终止信号
        SIGTERM(15) -> 可以被捕获或忽略的进程终止编号
        
      3. 向进程自己发送信号

        #include <stdlib.h>
        
        void abort(void);
        /*
        功能: 想进城发送SIGABRT(6)信号,该信号默认情况下可以使进程结束
        无返回值
        */
        

在使用exit函数或main函数正常退出时,如果注册了atexiton_exit,那就会触发退出函数,以下是示例代码

#include <stdio.h>
#include <stdlib.h>

void func(void)
{
    exit(6);
}

void goto1(void)
{
    printf("goto1\n");
}

void goto2(int status, void *arg)
{
    printf("status = %d\n", status);
    printf("arg = %s\n", (char *)arg);
}

int main(void)
{
    atexit(goto1);  // 退出之前执行goto1
    on_exit(goto2, "heihei");  // 退出之前执行goto2,可以传参
    func();
    return 0;
}

/*
相当于钩子函数,在退出之前执行,可以进行一些回收操作
status = 6
arg = heihei
goto1
*/

回收子进程

  • 如果不回收子进程的话,会导致有很多僵尸进程的存在,从而消耗更多的系统资源。
  • 父进程需要等待子进程到的终止,以继续后续工作
  • 父进程需要了解子进程终止的原因,是正常终止,还是异常终止

阻塞回收

  • wait函数是用于回收子进程的一个函数,它使用的是阻塞回收,使用它必须包含sys/wait.h头文件

  • wait函数

    • pid_t wait(int *status);

      • 功能:等待和回收任意子进程

      • 参数:status用于输出子进程的终止状态,可置NULL

        • 补充:可以使用以下工具宏分析子进程的终止状态

          if(WIFEXITED(status))
              // 真
              printf("正常终止: 进程退出码是%d\n", WEXITSTATUS(status));
          else
              // 假
              printf("异常终止: 终止进程的信号是%d\n", WTERMSIG(status));
          
          // 下面跟上面判断条件相反
          if(WIFSIGNALED(status))
              // 真
              printf("异常终止: 终止进程的信号是%d\n", WTERMSIG(status));
          else
              // 假
              printf("正常终止: 进程退出码是%d\n", WEXITSTATUS(status));
          
      • 返回值:成功返回回收的子进程PID,失败返回-1

  • 简单代码示例

    // 子进程的回收
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/wait.h>
    
    int main(void)
    {
        // 创建子进程
        pid_t pid = fork();
        if(pid == -1)
        {
            perror("fork");
            return -1;
        }
        // 子进程相关操作
        if(pid == 0)
        {
            printf("%d进程: 我是子进程!\n", getpid());
            // sleep(5);
            // exit(3);
            // _exit(5);
            // return 2;
            // abort();  // 向进程发送信号异常结束
            // 以下两句会造成内存无效访问,会返回11
            char *p = NULL;
            *p = 123;
        }
        // 父进程等待回收子进程
        printf("%d进程: 我是父进程!\n", getpid());
        int s;  // 用来输出所回收的子进程终止状态
        pid_t childpid = wait(&s);
        if(childpid == -1)
        {
            perror("wait");
            return -1;
        }
        printf("父进程回收了%d进程的僵尸!\n", childpid);
        // 根据返回值判断子进程是否是正常结束
        if(WIFEXITED(s))
            printf("正常结束: %d\n", WEXITSTATUS(s));
        else
            printf("异常结束: %d\n", WTERMSIG(s));
    
        return 0;
    }
    
  • 以下代码是一个循环创建5个进程,然后父进程挨个回收

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <errno.h>
    
    int main(void)
    {
        printf("%d进程: 我是父进程!\n--------------------------\n", getpid());
        sleep(1);
        // 创建子进程
        for(int i = 0; i < 5; i++)
        {
            pid_t pid = fork();
            if(pid == -1)
            {
                perror("fork");
                return -1;
            }
            // 子进程操作
            if(pid == 0)
            {
                printf("%d进程: 我是子进程!\n", getpid());
                sleep(i+1);
                return i+1;
            }
        }
        
        // 父进程操作: 回收子进程
        while(1)
        {
            int s;  // 用户接收子进程的终止状态
            pid_t childpid = wait(&s);
            if(childpid == -1)
            {
                if(errno == ECHILD)
                {
                    printf("没有子进程可以回收了!\n");
                    break;
                }
                else
                {
                    perror("wait");
                    return -1;
                }
            }
            // 判断子进程的终止状态
            if(WIFEXITED(s))
                printf("正常结束: %d\n", WEXITSTATUS(s));
            else
                printf("异常终止: %d\n", WTERMSIG(s));
        }
        return 0;
    }
    

非阻塞回收

  • waitpid函数一般用于非阻塞回收子进程,还可以回收特定子进程,使用这个函数需要引用sys/wait.h头文件

  • waitpid函数

    • pid_t waitpid(pid_t pid, int *status, int options);
      • 功能:等待并回收任意或特定子进程
      • 参数
        • pid:取-1等待并回收任意子进程,相当于wait函数,>0等待回收特定子进程
        • status:用于输出子进程的终止状态,可置NULL
        • option:0代表阻塞模式,WNOHANG代表非阻塞模式,如果等待的进程还在运行,则返回0
      • 返回值:成功返回回收子进程的PID或者0,失败返回-1
  • 以下是使用非阻塞回收的方法回收子进程

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    #include <errno.h>
    
    int main(void)
    {
        printf("%d进程: 我是父进程!\n-------------------------\n", getpid());
        // 创建子进程
        for(int i = 0; i < 5; i++)
        {
            pid_t pid = fork();
            if(pid == -1)
            {
                perror("fork");
                return -1;
            }
            // 子进程相关操作
            if(pid == 0)
            {
                printf("%d进程: 我是子进程!\n", getpid());
                sleep(i+1);
                // 三种效果
                if(i == 3)
                {
                    abort();
                }
                else if(i == 4)
                {
                    char *p = NULL;
                    *p = 123;
                }
                else
                {
                    return i+1;
                }
            }
        }
        
        // 父进程回收子进程
        sleep(1);
        while(1)
        {
            int s; // 用于保存进程的终止状态
            pid_t childpid = waitpid(-1, &s, WNOHANG);  // 这里使用的是非阻塞模式
            if(childpid == -1)
            {
                // 报错或者没有子进程了
                if(errno == ECHILD)
                {
                    printf("没有子进程了!\n");
                    break;
                }
                else
                {
                    perror("waitpid");
                    return -1;
                }
            }
            else if(childpid == 0)
            {
                // 子进程还在运行
                printf("子进程在运行,无法回收,先睡会!\n");
                sleep(2);
            }
            else
            {
                // 回收成功并判断是否正常终止
                printf("%d子进程回收成功!\n", childpid);
                if(WIFEXITED(s))
                    printf("%d进程正常终止, 进程退出码: %d\n\n", childpid, WEXITSTATUS(s));
                else
                    printf("%d进程异常终止, 终止进程信号: %d\n\n", childpid, WTERMSIG(s));
            }
        }
        return 0;
    }
    
    /*
    代码执行效果
    1761797进程: 我是父进程!
    -------------------------
    1761798进程: 我是子进程!
    1761799进程: 我是子进程!
    1761800进程: 我是子进程!
    1761801进程: 我是子进程!
    1761802进程: 我是子进程!
    子进程在运行,无法回收,先睡会!
    1761798子进程回收成功!
    1761798进程正常终止, 进程退出码: 1
    
    1761799子进程回收成功!
    1761799进程正常终止, 进程退出码: 2
    
    子进程在运行,无法回收,先睡会!
    1761800子进程回收成功!
    1761800进程正常终止, 进程退出码: 3
    
    1761801子进程回收成功!
    1761801进程异常终止, 终止进程信号: 6
    
    子进程在运行,无法回收,先睡会!
    1761802子进程回收成功!
    1761802进程异常终止, 终止进程信号: 11
    
    没有子进程了!
    */
    

补充

  • 实际情况下,无论进程是正常终止还是异常终止,都会通过系统内核向其父进程发送一个SIGCHLD(17)信号,我们可以提供一个针对该信号的处理函数,在信号处理函数中异步的方式回收子进程,这样不仅流程简单,回收效率还高,僵尸进程的存活时间也会很短。

创建新进程

与fork函数不同,这里使用的exec函数是创建一个新的进程,新进程会取代调用自身的进程,新进程覆盖之前的进程地址空间,进程的PID不会改变。
在这里插入图片描述

  • exec不是一个函数,而是一堆函数,功能一样,用法相似

  • #include <unistd.h>

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

      execl("/bin/ls", "ls", "-a", "-l", NULL);
      /*
      path使用的是路径名
      使用NULL作为arg的结尾
      失败返回-1,成功不返回
      */
      
    2. int execlp(const char *file, const char *arg, ...);

      execlp("ls", "ls", "-a", "-l", NULL);
      /*
      file使用的是文件名,会从环境变量中一个个的找
      使用NULL作为arg的结尾
      失败返回-1,成功不返回
      */
      
    3. int execle(const char *path, const char *arg, ..., char *const envp[]);

      char *envp[] = {"NAME=bhlu", "AGE=25", NULL};
      execle("/usr/bin/env", "env", NULL, envp);
      /*
      比excel多一个envp,用于设置环境变量,它设置什么,新进程的环境变量就只有什么
      失败返回-1,成功不返回
      环境变量输出:
          NAME=bhlu
          AGE=25
      */
      
    4. int execv(const char *path, char *const argv[]);

      char *argv[] = {"ls", "-a", "-l", NULL};
      execv("/bin/ls", argv);
      /*
      execv系列使用的都是字符指针数组,字符数组是以NULL结尾
      失败返回-1,成功不返回
      */
      
    5. int execvp(const char *file, char *const argv[]);

      char *argv[] = {"ls", "-a", "-l", NULL};
      execvp("ls", argv);
      /*
      跟execv差不多,就第一个参数是文件名
      失败返回-1,成功不返回
      */
      
    6. int execve(const char *path, char *const argv[], char *const envp[]);

      char *argv[] = {"env", NULL};
      char *envp[] = {"NAME=bhlu", "AGE=25", NULL};
      execve("/usr/bin/env", argv, envp);
      /*
      跟execle函数差不多,就是这里的第二个参数是字符指针数组
      失败返回-1,成功不返回
      */
      
  • 后缀不同,代码的含义也不同

    • l:即list,新进程的命令以字符指针列表形式传入,列表以空指针结束
    • p:即path:第一个参数,不包含/,就根据PATH环境变量搜索文件
    • e:即environment:设定环境变量,不指定则从调用进程复制
    • v:即vector:新进程的命令行参数以字符指针数组的形式传入,数组以空指针结束
    • 实际底层最后使用的都是execve函数
  • 使用exec函数基本会将原进程的所有信号、属性、数据等都丢失或者恢复初识状态,只有PIDPPIDUID等会被继承下来。

  • 一般都会先创建一个子进程,然后在子进程中使用exec函数,以下是相关示例

    #include <stdio.h>
    #include <unistd.h>
    
    int main(void)
    {
        // 创建子进程
        pid_t pid = fork();
        if(pid == -1)
        {
            perror("fork");
            return -1;
        }
    
        // 子进程相关操作
        if(pid == 0)
        {
            char *argv[] = {"env", NULL};
            if(execvp("/bin/env", argv) == -1)
            {
                perror("execvp");
                return -1;
            }
        }
    
        // 父进程操作
        printf("父进程PID: %d\n", getpid());
        return 0;
    }
    

system

  • 下面介绍的是c语言执行shell命令的函数

  • #include <stdlib.h>

    • int system(const char *command);
      • 功能:执行shell命令
      • 参数:shell命令,如果参数取NULL,返回非0表示Shell可用,返回0表示不可用
      • 返回值:成功返回command进程的终止状态, 失败返回-1
  • 代码实例

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(void)
    {
        int s = system("echo $PATH");
        if(s == -1)
        {
            perror("system");
            return -1;
        }
    
        printf("父进程PID: %d\n", getpid());
        return 0;
    }
    
  • system函数内部调用了vforkexecwaitpid等函数,而且它是标准库函数,可以跨平台使用

    • 如果调用vforkwaitpid函数出错,则返回-1
    • 如果调用exec函数出错,则在子进程中执行exit(127)
    • 如果都成功,会从waitpid获取command进程的终止状态

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

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

相关文章

分析一段js加密代码

源代码 (function(){var KBP,EbW482-471;function wHY(r){var y2043987;var lr.length;var a[];for(var g0;g<l;g){a[g]r.charAt(g)};for(var g0;g<l;g){var vy*(g289)(y%39401);var ty*(g287)(y%31258);var xv%l;var pt%l;var ma[x];a[x]a[p];a[p]m;y(vt)%2251814;};re…

华为云云耀云服务器L实例评测|云耀云服务器L实例部署推箱子经典小游戏

[TOC](华为云云耀云服务器L实例评测&#xff5c;云耀云服务器L实例部署推箱子经典小游戏 一、前言二、Sokoban小游戏介绍2.1 Sokoban小游戏简介2.2 Sokoban小游戏玩法 三、本次实践介绍3.1 本次实践简介3.2 本次环境规划 四、购买云耀云服务器L实例4.1 购买云耀云服务器L实例4.…

跟着Nature Plant学图形颜色搭配 | caecopal包

写在前面 今天在Nature Plant(IF:16.0)期刊中看到文中的图形&#xff0c;进一步的查看后发现作者使用一个R包来进行图形颜色的搭配。就此机会也分享给大家&#xff0c;若你需要可以进一步查看及使用此包。 对于图形颜色的搭配&#xff0c;对于文章整体美观是非常重要。但是&a…

坐标系上的交互+分治与交互:CF788D

https://codeforces.com/contest/788/problem/D 坐标系上的交互有一种常见套路&#xff0c;就是抓住一些关键的线 x轴y轴yx&#xff08;就是此题&#xff09; 然后考虑接下来怎么做。 交互题常见有二分的套路&#xff0c;此题我们可以考虑推广到分治。 不断判断mid&#xf…

修改sqlmap-Tamper脚本

修改sqlmap-Tamper脚本 文章目录 修改sqlmap-Tamper脚本1 sqlmap官网2 sql注入漏洞注入尝试3 环境&#xff1a;sqli-labs/Less-26a/3.1 尝试宽字节注入: 3.2 sqlmap使用3.3准备修改sqlmap使用 4 sqlmap中-tamper工厂&#xff08;输入输出&#xff09;4.1 [参考文章&#xff1a;…

用MFC写的OCX,在HTML调用事件有响应,多线程中调用时网页上事件无响应 :用消息通知来更新可以

问题:连接成功后&#xff0c;点交易无事件。但每次点连接都有事件。直接点交易也有事件。好像跟延时有关系。 Sleep(10000)可以&#xff0c;跟延时没关系。 固定参数也无响应&#xff0c;跟参数没关系。 可能是多线程有关系。用消息通知来更新可以。 void Callback(int code…

git创建仓库并建立远程连接

创建仓库 以gitee为例 首先需要创建一个项目在gitee 中 创建仓库之后 建立连接 git init 初始化项目 git add . // 将所有文件存储到暂存区 git commit -m 自己定义的名字 // 本地提交更新 git remote add origin 远程仓库地址 // 之后就将本地和远程仓库建立连…

JavaScript 基础第三天笔记

JavaScript 基础第三天笔记 if 多分支语句和 switch的区别&#xff1a; 共同点 都能实现多分支选择&#xff0c; 多选1大部分情况下可以互换 区别&#xff1a; switch…case语句通常处理case为比较确定值的情况&#xff0c;而if…else…语句更加灵活&#xff0c;通常用于范围…

无线通信——Mesh的最后一公里问题

其实“最后一公里”问题直到现在也是在探究的话题。首先解释一下什么是“最后一公里”&#xff1a;我们下班了&#xff0c;假如公司有专车把我们送到了地铁站。这非常的方便&#xff01;从地铁站坐上回家方向的地铁。也是很省事儿&#xff01;虽然中途在地铁需要换站&#xff0…

亚信科技AntDB数据库 高并发、低延迟、无死锁,深入了解AntDB-M元数据锁的实现

AntDB-M在架构上分为两层&#xff0c;服务层和存储引擎层。元数据的并发管理集中在服务层&#xff0c;数据的存储访问在存储引擎层。为了保证DDL操作与DML操作之间的一致性&#xff0c;引入了元数据锁&#xff08;MDL&#xff09;。 AntDB-M提供了丰富的元数据锁功能&#xff0…

华为鸿蒙4,3,2禁用Google谷歌服务框架,使用最新谷歌Google play服务

华为鸿蒙4,鸿蒙3,鸿蒙2有一些应用或者游戏尤其是游戏需要最新版的谷歌play服务支持才可以使用。但是华为的鸿蒙系统解决完”设备未经 play 保护机制认证”之后,最稳定的谷歌play服务是20这个版本&#xff0c;一旦升级到最新的Google谷歌play服务&#xff0c;目前是最新版本23这…

LabVIEW应用开发——VI模块化

我们在写C语言的时候&#xff0c;一些模块化的功能或者多次调用的功能&#xff0c;我们一般会用一个函数封装起来&#xff0c;方便使用并且让代码看起来更加的简洁。这种函数一般都会包含这几个概念&#xff0c;输入参数、输出参数和返回值。而LabVIEW的VI就可以当作是一个函数…

led台灯哪个牌子最好?2022最新的台灯牌子排名

想要选好护眼台灯首先我们要知道什么是护眼台灯&#xff0c;大的方向来看&#xff0c;护眼台灯就是可以保护视力的台灯&#xff0c;深入些讲就是具备让灯发出接近自然光特性的光线&#xff0c;同时光线不会伤害人眼而出现造成眼部不适甚至是视力降低的照明设备。 从细节上看就…

EasyX趣味化编程note2,绘制基本图形

创意化编程&#xff0c;让编程更有趣 今天介绍的仍为比较简单的效果&#xff0c;由浅入深来进行学习 介绍每个函数都会附上代码和运行结果&#xff0c;感兴趣的大家可以复制粘贴运行一下看看效果&#xff0c;也可以自己进行改动&#xff0c;非常好玩且加深印象。 上节课的知识…

wsl2 更新报错问题解决记录

1、问题 win10 中安装的 wsl2&#xff0c;启动 docker desktop 时提示 wsl2 有问题&#xff1a; 于是点击推荐的地址连接到微软&#xff0c;下载 wsl2 的更新文件。之后运行&#xff0c;又报错&#xff1a; 更新被卡住。 2、解决方法 WinR 输入 cmd 打开命令行窗口&#x…

基于Vue+ELement搭建动态树与数据表格实现分页模糊查询

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《ELement》。&#x1f3af;&#x1f3af; &#x1…

self-XSS漏洞SRC挖掘

本文由掌控安全学院 - 一朵花花酱 投稿 Markdown是一种轻量级标记语言&#xff0c;创始人为约翰格鲁伯&#xff08;John Gruber&#xff09;。它允许人们使用易读易写的纯文本格式编写文档&#xff0c;然后转换成有效的 XHTML&#xff08;或者HTML&#xff09;文档。这种语言吸…

MySQL数据库基本操作--DDL

文章目录 1. DDL2. 对数据库的常用操作3. 对表结构的常用操作数据类型数值类型字符串类型日期类型 基本操作修改表结构格式 1. DDL DDL(Data Definition Language)&#xff0c;数据定义语言&#xff0c;该语言部分包括以下内容&#xff1a; 对数据库的常用操作对表结构的常用…

我与南非的三次邂逅

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 卷圈 运营 / SandLiu 卷圈 监制 / 姝琦 产品统筹 / bobo 场地支持 / 声湃轩天津录音间 特别感谢 / 南非旅游局、日光派对 本期节目中&#xff0c;我们邀请了一位特别的嘉宾索导&#xff0c;她将带领我们走进南非&…

ide unknown command (empty parse result): / 问题及解决方案

vs studio端口冲突了 在属性 web 修改端口号就能解决 搞定