Linux系统编程:进程的创建、终止和替换

news2025/1/12 15:45:08

目录

一. 进程创建

1.1 fork函数的使用

1.2 fork函数的底层实现

1.3 子进程创建的写时拷贝问题

二. 进程的退出

2.1 进程退出的场景和方法

2.2 exit和_exit函数

三. 进程的等待

3.1 为什么要有进程等待

3.2 进程等待的方法

3.2.1 进程等待的相关函数

3.2.2 进程的阻塞等待

3.2.3 子进程的退出状态

3.2.4 子进程的非阻塞等待

四. 总结


一. 进程创建

1.1 fork函数的使用

Linux提供了创建子进程的系统调用接口fork,fork函数的相关信息如下:

  • 函数原型:pid_t fork(void)
  • 函数返回的:如果子进程创建成功,那么给父进程返回子进程的pid,给子进程返回0;如果子进程创建失败,那么返回-1。

fork函数创建子进程,一般做以下用途:

  • 希望父子进程共享代码,子进程和父进程执行不同的程序。
  • 让一个进程执行不同的程序。
  #include<iostream>    
  #include<sys/types.h>    
      
  int main()    
  {    
      pid_t ret = fork();  //子进程创建    
      
      if(ret < 0)  //创建失败    
      {    
          //...     
      }    
      else if(ret == 0)    
      {     
          //子进程代码     
      }    
      else    
      {    
          //父进程代码    
      }    
      
      return 0;                                                                                                                      
  }  

1.2 fork函数的底层实现

当一个进程调用fork函数时,操作系统的内核会进行如下的操作:

  • 分配新的内存空间和内核数据结构给子进程。
  • 将父进程中的部分数据/代码、数据结构拷贝到子进程的空间中去。
  • 将子进程的PCB添加到系统的进程列表中去。
  • fork函数返回,进程开始被调度。

如图1.1所示,在fork之后,父进程的指向流一份为二,fork函数会给父子进程都返回一个特定的值,父子进程都会执行fork之后的代码。

图1.1 fork调用前后的执行流

1.3 子进程创建的写时拷贝问题

当一个进程要被CPU运行时,需要将它的代码和数据加载到内存中去,但是,通过fork创建的子进程没有自己的代码和数据,那么它就只能从父进程中获取代码和数据。

结论:fork创建的子进程的代码和数据是从父进程中获取的。

但是,父子进程之间需要保证各自的独立性,因此,子进程肯定不能单纯的与父进程共享代码和数据,肯定要进行相应的拷贝。

如果子进程创建时将父进程的代码和数据全都拷贝下来,那么就会存在大量的CPU资源消耗和内存资源浪费,根据代码和数据的权限的区分:

  • 代码:只有读权限。
  • 数据:有读和写的权限。

我们可以认为,子进程和父进程共享代码,因为代码只有读的权限,子进程和父进程中肯定不会对代码做任何修改,即使共享,也不会影响父子进程的独立性。

这样,我们是否就可以认为,父子进程独享数据?CPU是不是会将父进程的全部数据都拷贝一份给子进程?答案显然是否定的。

这是因为,子进程中不一定会访问到父进程的全部数据,即使进行了访问,也很有可能只是读取。既然这样,操作系统会不会在子进程完成创建之前,识别父子进程对那些数据进行了写操作,然后将进行了写操作的数据拷贝一份给子进程?答案也是否定的。因为即使fork之后对某个数据进行了写操作,也不一定会在fork之后立马访问这个数据,那么如果立马为子进程拷贝一份这个数据,就会存在内存资源浪费的问题。

因此,子进程在继承父进程数据时遵循写时拷贝的原则,当发现fork之后的代码对某个数据进行了写操作时,再去对这个数据进程拷贝。

写时拷贝,能最大化利用物理内存资源,是高效使用内存资源的一种表现。

图1.2 父子进程间数据的写时拷贝

提问:父子进程,是共享全部的代码,还是只共享fork之后的代码?

答:是共享全部的代码

但是,程序在fork之后,子进程其实只执行了fork之后的代码,那么既然父子进程共享全部代码,子进程为什么不是从main()函数的起始位置执行。

这是由于CPU内部有一个EPI寄存器,有些地方也被称为pc(程序计数器),EPI无论在任何时候,都会存储当前所执行指令的下一条指令的地址。而每个进程PCB中,都会存有程序计数器信息。

fork创建的子进程,需要以父进程的PCB为模板,建立自己的PCB,那么,子进程PCB就会继承下来父进程的程序计数器内容,这样就实现了从fork之后开始执行代码。

因为进程的执行随时可能会被中断,当这个进程下次再开始执行的时候,需要接着当前的位置开始执行,因此,当某个进程中断,必须从CPU中带走它的程序计数器信息,以记录进程下次开始运行时的位置。

二. 进程的退出

2.1 进程退出的场景和方法

提问:进程终止时,操作系统进行了哪些工作?答:释放了进程的数据、代码、内核数据结构等相关资源。

进程可能在以下三种场景发生时退出:

  • 代码运行结束,结果正确。
  • 代码运行结束。结果错误。
  • 进程异常终止(野指针、越级访问、Ctrl + C强制进程终止等)。

我们首先来讨论代码正常结束时的场景。

在语言层面学习C/C++时,我们在main函数的最后一行,一般都会写return 0,那么有没有考虑过,为什么要return 0,而不是return 1、return 2等?

要解释上面的问题,首先要明白main函数返回值的本质是什么。一个程序加载到内存中开始运行时,系统中就产生了一个对应的进程,main函数返回,就意味着进程终止。main函数的返回值,在操作系统层面,就是进程的退出码,它是反馈给操作系统的。一般来说,默认退出码为0时,运行结果正确,进程正常终止,退出码非0则表示进程运行代码的结果不正确。

如果程序正常执行,得到了想要的结果,我们一般不关注具体的实现,因而用0表示正常运行并结束的进程,如果进程中出现了异常,我们往往需要比较详细的进程错误信息,由于非0值有无数个,那么每一个非0的退出码都可以表示一种错误信息。

  • strerror(int errnum)函数:以字符串的形式获取进程退出码对应的错误信息。
  • echo $?指令:获取上个终止的进程的退出码。

如果程序异常终止(编译器杀掉进程),那么该进程就会收到一个对应的错误信号,如果进程异常终止,那么进程的退出码将不再有意义。 

根据正常终止进程与异常终止进程,有以下的进程终止方法:

  • 正常终止进程:main函数return、调用exit函数、调用_exit函数。
  • 异常终止进程:Ctrl + C、kill -9 [pid]、进程异常崩溃。

2.2 exit和_exit函数

_exit是Linux提供的终止进程的系统接口,exit是C标准库中提供的进程终止函数,其底层是通过封装系统接口_exit来实现的,_exit和exit的区别如下:

  • 调用_exit时系统直接杀死进程,不会附带任何其他的操作。
  • 掉用exit时,会先执行用户自定义的资源清理相关函数、刷新缓冲区,然后才会杀死进程。

看下面的代码,代码段1在printf("hello")后使用exit()退出进程,printf的内容被成功输出到了屏幕上,这是因为使用exit函数退出进程时会刷新缓冲区,而使用_exit函数退出进程就不会输出hello,这是因为_exit直接杀死进程,并没有将缓冲区的内容刷新处理。

代码段1:-- 成功输出hello
#include<stdio.h>    
#include<stdlib.h>                                                                                                                                        
int main()    
{    
    printf("hello");    
    exit(10);    
} 

代码段2:-- 不输出hello
#include<stdio.h>    
#include<unistd.h>                                                                                                                                        
#include<stdlib.h>    
int main()    
{    
    printf("hello");    
    _exit(10);    
} 

图2.1 exit和_exit函数终止进程时进行的操作

总结:用代码退出进程的三种方式

  1. return 返回进程退出码,进程结束。
  2. 调用exit函数,退出进程同时进行资源清理、刷新缓冲区相关操作。
  3. 调用_exit函数,直接强制进程终止。

return和exit的区别:return结束进程,只能是在main函数结束的使用通过return返回退出码的形式,任何一个在main函数中被调用的函数return都只能被解释为函数的返回值,而不是退出码。exit可以在进程代码的任意位置被调用,即使是在main函数调用的函数中,也可以使用exit来强制进程终止。

三. 进程的等待

3.1 为什么要有进程等待

进程等待,起始就是父进程等待子进程状态发生改变(子进程终止退出),对子进程的退出信号、退出码等相关信息进行检查,以确保将子进程从系统中移除,避免僵尸进程。僵尸进程,会引发内存泄漏问题。

僵尸进程如何引发内存泄漏:

如果一个子进程退出了但是父进程没有获取子进程的退出状态信息,即没有对子进程进行任何处理,那么这个子进程就会称为一个僵尸进程。注意,僵尸进程内存泄漏,并不是动态申请的内存资源获取占用的栈区资源泄漏,相反子进程终止它所申请的资源都会被OS自动回收。但是,如果父进程不对子进程进行处理,那么子进程就一直处于僵尸状态,子进程的PCB就要一直在内存中记录僵尸状态,僵尸进程的PCB所占用的内存资源就一直得不到释放,从而引发内存泄漏。

结论:僵尸进程引发内存泄漏的原因是这个进程的PCB一直占用内存空间不释放。

如果创建子进程的父进程会很快终止,那么子进程即使僵尸,它的PCB所占用的内存空间也会在父进程终止时被OS回收。但是,对于需要长期运行的软件,或者是服务器程序,由于父进程可能会长期不断的运行,那么僵尸进程所占用的内存资源就一直得不到释放,从而引发内存泄漏。

结论:僵尸进程的危害主要体现在需要长期不间断运行的软件程序上。

提问:对于不需要长期运行的程序,是否可以不进行进程等待,父进程不接收子进程的退出信息?答案是否定的,因为在有些特定的场景下,父进程需要获取子进程的相关退出信息,以判断子进程是否是正常终止,或者运行结果是否正确。

3.2 进程等待的方法

进程等待的方法可分为阻塞等待和非阻塞等待。

3.2.1 进程等待的相关函数

有两个系统调用函数,可以实现进程的等待,他们分别wait和waitpid:

  • wait函数,原型为pid_t wait(int* status)
  • waitpid函数,原型为pid_t wait(int id, int* status, int option)

wait函数可以实现对任意子进程的阻塞等待,参数status为输出型参数,用于记录进程退出的退出信号和进程的退出码,如果不关注子进程退出状态status可设为NULL。

waitpid可以实现对指定id的进程的阻塞等待和非阻塞等待,参数id为指定等待的子进程的id,如果给id传-1则可以实现对任何子进程的等待,status为输出型参数用于记录进程的退出信号和进程的退出码,option用于选择阻塞等待还是非阻塞等待,如果option给传0是阻塞等待,如果传WNOHANG则为阻塞等待,WNOHANG为Linux系统定义的宏。

wait函数返回值及意义:如果子进程等待成功,wait返回子进程id,子进程等待失败返回-1。

waitpid函数返回值及意义:如果等待子进程失败(可能的原因为指定id不存在,或该父进程根本没有创建子进程),则返回-1。如果等待成功,如果子进程没有终止返回0,终止了返回子进程id。

问题一:为什么要通过wait/waitpid获取子进程的退出码,而不采用全局变量?答:这是因为进程具有独立性,在子进程中改变某个全局变量的值并不会影响父进程中的这个全局变量的值,因而也就无法通过全局变量来获取子进程退出码。况且,不能单纯通过子进程退码来判断子进程退出状态,还应该获取退出信号。

问题二:wait/waitpid如何获取子进程退出状态和退出信号?答:由于父子进程之间相互独立,那么wait/waitpid肯定不是通过子进程的代码和数据获取子进程退出状态的。子进程退出时会将退出信号和退出码写到它的PCB中,wait/waitpid从子进程的PCB中获取退出状态信息。(PCB中含有int exit_code 和 int exit_signal记录退出码和退出信号)

3.2.2 进程的阻塞等待

这里分别使用wait来演示如何实现进程的阻塞等待以及阻塞等待时进程的执行逻辑。在代码3.1中,子进程执行5次while循环,每层while循环内部打印子进程的pid和ppid,并且休眠一秒。父进程中使用wait函数等待子进程终止,如果wait等待成功,就打印相关信息,然后打印hello world。

代码运行的结果表明(图3.1),wait后面的代码在子进程终止前并不会执行,如果某个父进程调用wait函数,那么它的PCB会被OS放到子进程的阻塞队列中去等待子进程退出,直到子进程退出,父进程wait之后的代码才会运行。

如果父进程中使用wait或waitpid等待子进程成功,那么子进程就会彻底地被销毁,不会存在僵尸进程问题。

代码3.1:wait阻塞等待 

#include<iostream>    
#include<unistd.h>    
#include<sys/types.h>    
#include<sys/wait.h>    
    
int main()    
{    
    pid_t id = fork();  //创建子进程    
    
    if(id < 0)  //如果子进程创建失败    
    {    
        perror("fork");    
        return 1;    
    }    
    else if(id == 0)  //子进程运行代码    
    {    
        int cnt = 5;    
        while(cnt)    
        {    
            printf("Child Process, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);    
            sleep(1);    
        }    
    }    
    else //父进程代码    
    {    
        pid_t res = wait(NULL);  //等待子进程,不关注子进程退出信息    
        if(res > 0)    
        {    
            printf("子进程等待成功\n");    
        }    
        else    
        {    
            printf("子进程等待失败\n");    
        }    
    
        printf("hello\n");    
        printf("hello\n");                                                                                                                                
    }    
    
    return 0;    
}   
图3.1 代码3.1的运行结果

3.2.3 子进程的退出状态

无论是wait还是waitpid函数,如果关注子进程的退出状态,就使用输出型参数的status,记录子进程的退出码和退出信号。

  • 退出码一般用于判断子进程运行结果是否正确。
  • 退出信号用于判读子进程是将所有代码运行完成后退出的,还是异常终止。如果子进程异常终止,即退出信号不为0,那么退出码将不再有任何意义。

wait/waitpid函数,将进程退出信号和退出码,记录在输出参数statu的不同比特位:

  • 无论子进程异常终止还是正常终止,status的高16位都没有任何价值。
  • 如果子进程退出正常,那么第0~7位均为0,第8~15位记录子进程退出码。
  • 如果子进程异常退出,那么第0~6位记录退出信号,第7位记录core dump标志,即:gdb调试程序崩溃的信号。

通过 status & 0x7F,可以拿到进程的退出信号,通过 (status >> 8) & 0xFF 可以拿到退出码。

图3.2 statu的bit位使用情况

如代码3.2所示,子进程通过exit(10)退出,退出码10,在父进程中,使用int型变量statu作为输出型参数接收子进程返回的状态信息,通过status & 0x7F和(status >> 8) & 0xFF 获取进程的退出信号和退出码并printf打印。运行代码,父进程中显示子进程的退出信号0,退出码10。

代码3.2:检测子进程退出状态信息 

#include<iostream>    
#include<unistd.h>    
#include<sys/types.h>    
#include<sys/wait.h>    
    
int main()    
{    
    pid_t id = fork();    
    
    if(id < 0)    
    {    
        perror("fork");    
        return 1;    
    }    
    else if(id == 0)                                                                                                                                      
    {    
        int cnt = 3;    
        while(cnt--)    
        {    
            printf("child process, pid:%d. ppid:%d\n", getpid(), getppid());    
            sleep(1);    
        }    
        exit(10);    
    }    
    else    
    {    
        int status = 0;    
        pid_t ret = waitpid(id, &status, 0);    
        if(ret > 0)    
        {    
            printf("成功等待子进程退出, 退出信号:%d, 退出码:%d\n", status & 0x7F, (status >> 8) & 0xFF);    
        }    
    }    
    
    return 0;    
} 

上面的方法获取子进程退出信息相对复杂,有没有简单的方法呢?答案是有的,Linux为此定义了两个宏,用于取得进程退出信息:

  • WIFEXITED(statu):如果子进程正常终止返回真,否则返回假。
  • WEXITSTATUS(statu):如果子进程正常终止(WIFEXITED非0),那么返回进程退出码。

代码3.3,展示了这两个宏的用法,一般先通过WIFEXITED判断子进程是否正常终止,然后再通过WEXITEDSTATUS获取子进程的退出码。

代码3.3:WIFEXITED和WEXITEDSTATUS的使用

  int main()    
  {    
      pid_t id = fork();    
      
      if(id < 0)    
      {    
          perror("fork");    
          return 1;    
      }    
      else if(id == 0)    
      {    
          int cnt = 3;    
          while(cnt--)    
          {    
              printf("child process, pid:%d. ppid:%d\n", getpid(), getppid());    
              sleep(1);    
          }    
          exit(10);    
      }    
      else    
      {    
          int status = 0;    
          pid_t ret = waitpid(id, &status, 0);    
      
          if(WIFEXITED(status))    
          {    
              printf("子进程正常终止,退出码:%d\n", WEXITSTATUS(status));                                                                                 
          }    
          else    
          {    
              //子进程异常终止    
              perror("wait");    
          }    
      }    
      
      return 0;    
  }   

3.2.4 子进程的非阻塞等待

如果我们将waitpid的最后一个参数设置为WNOHANG,那么就waitpid就进行非阻塞等待。非阻塞等待期间,可以继续执行父进程的代码。

这里通过轮巡检测技术,来实现对子进程的非阻塞等待,即:通过while循环,每隔一段时间就对子进程状态进行一次检查,直到子进程终止,或是waitpid等待子进程失败。

代码3.4:子进程阻塞等待

std::vector<handler> hand;    
void hand_push()    
{    
    hand.push_back(func1);    
    hand.push_back(func2);    
}    
    
int main()    
{    
    pid_t id = fork();    
    
    if(id < 0)    
    {    
        perror("fork");    
        return 1;    
    }    
    else if(id == 0)    
    {    
        int cnt = 3;    
        while(cnt--)    
        {    
            printf("child process, pid:%d. ppid:%d\n", getpid(), getppid());    
            sleep(1);    
        }    
        exit(10);    
    }    
    else    
    {    
        int status = 0;    
        int quit = 0;   //记录子进程是否成功退出    
    
        while(!quit)    
        {    
            pid_t ret = waitpid(id, &status, WNOHANG);    
    
            if(ret < 0)    
            {    
                printf("等待子进程失败\n");    
            }    
            else if(ret == 0)    
            {    
                printf("子进程等待成功,但子进程还未终止,父进程可以执行一些操作\n");    
                if(hand.empty()) hand_push();    
                for(const auto& e : hand)                                                                                                                 
                {    
                    e();    
                }    
            }    
            else    
            {
                printf("子进程等待成功,退出!\n");
                printf("退出码:%d\n", WEXITSTATUS(status));
                quit = 1;
            }

            sleep(1);
        }
    }

    return 0;
}

四. 总结

  • fork函数可以用于创建子进程,如果创建失败返回-1,如果创建成功给父进程返回子进程的id,给子进程返回0。
  • fork函数创建的子进程与父进程共享一份代码,子进程获取数据时,遵循写时拷贝原则,写时拷贝可以最大程度上提高物理内存资源的利用效率。子进程的PCB是以父进程的PCB为模板获取的,会拿走父进程的程序计数器(CPU的EIP寄存器中的数据),保证fork之后的子进程从fork之后的代码开始执行。
  • 进程有三种退出场景:程序运行正常结束,结果正确、程序运行正常结束,结果不正确、程序异常终止。
  • main函数的return值,本质上为进程的退出码,一般认为退出码为0程序运行结果正确,退出码非0不正确。如果程序异常退出,那么退出信号非0,此时退出码便不再有意义。exit/_exit函数可以强制中断进程,exit是C语言提高的进程终止库函数,_exit是Linux提供的系统接口函数,exit函数是通过封装_exit来实现的。
  • 进程等待的目的是避免僵尸进程,通过wait和waitpid可以实现阻塞等待和非阻塞等待。

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

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

相关文章

JS知识点汇总(六)--作用域链this

1. 什么是作用域链&#xff1f; 作用域&#xff0c;即变量&#xff08;变量作用域又称上下文&#xff09;和函数生效&#xff08;能被访问&#xff09;的区域或集合 换句话说&#xff0c;作用域决定了代码区块中变量和其他资源的可见性 1. 作用域分类 一般将作用域分成&#x…

TDengine 发布 IoT 场景下 3.0 性能对比分析报告,全方位超越 InfluxDB TimescaleDB

6 月 26 日&#xff0c;涛思数据旗下时序数据库&#xff08;Time Series Database&#xff09; TDengine 正式发布 IoT 场景下 TDengine 3.0 性能对比分析报告&#xff0c;该报告在 IoT 场景下从数据写入、压缩和查询等维度&#xff0c;对比了 TDengine 与市场其他流行的时序数…

Linux系统安装QQ最新版

腾讯在2023-05-30更新了linux版的qq&#xff0c;这次界面终于不再复古&#xff0c;好看多了。 安装步骤&#xff1a; 1.进入官网&#xff0c;寻找合适的安装包下载 https://im.qq.com/linuxqq/index.shtml 选择跟自己计算机匹配的版本&#xff0c;一般都是X86&#xff0c;如…

VMware共享文件夹

当虚拟机需要使用宿主机里的文件时&#xff0c;就需要在虚拟机设置里添加共享文件夹&#xff0c;大概过程如下&#xff1a; 虚拟机设置&#xff1a; 在centos里&#xff0c;完成上述操作后会生成一个目录 /mnt/hgfs 宿主机里的文件就在这个目录里可以看到并使用了。

编译linux内核(二)

编译linux内核 1. 准备工作1.1 下载内核文件1.2 环境准备1.3 内核命名规则 2. 编译内核2.1 升级gcc2.2 make menuconfig其他报错2.3 配置选项2.4 编译内核2.5 安装模块2.6 安装内核2.7 验证内核 3. 制作内核文件3.1 创建磁盘文件3.2 磁盘分区3.3 将磁盘分区关联到/dev/loop7设备…

化学理论知识vr沉浸式教学软件推动立足“学生老师双主体”一体化的教学模式改革

VR虚拟仿真是基于虚拟现实、3D技术、计算机技术等搭建起来的一套具有数字化、智慧化的智能教学系统&#xff0c;它以学生学习为中心&#xff0c;探索学科内容&#xff0c;使用VR虚拟现实将学科内容以3D立体化形式呈现&#xff0c;培养学生的思维创新、实操技能。 VR虚拟仿真技术…

【数据库基础】Mysql基本概念讲解与实操

文章目录 数据库基础服务器&#xff0c;数据库&#xff0c;表关系Mysql的架构Sql分类 库的操作修改默认的编码格式两种校验修改数据库删除数据库备份和恢复观察用户&#xff0c;查看连接 表的操作修改表字段长度删除某一列修改表名修改列名称 数据类型小数类型floatdecimal字符…

Hystrix

一、Hystrix(豪猪)简介 1、Hystrix的设计目的 &#xff08;1&#xff09;对依赖服务调用时出现的调用延迟和调用失败进行控制和容错保护。 &#xff08;2&#xff09;阻止某一个依赖服务的故障在整个系统中蔓延&#xff0c;服务A->服务B->服务C&#xff0c;服务C故障了…

MySQL - Delete 和 Truncate 的区别

1. DELETE 命令 语法 &#xff1a; delete from 表名 [where 条件] -- 删除数据 (避免这样写, 会全部删除) DELETE FROM student;-- 删除指定数据 DELETE FROM student WHERE id 1; 2. TRUNCATE 命令 作用 : 完全清空一个数据库表, 表的结构和索引约束不会变. -- 清空 stu…

大体积mbtiles影像地图文件用什么软件浏览?

mbtiles格式简称mbt&#xff0c;是一种影像地图文件。PC端和手机端都有软件可以加载留着mbt格式的影像地图。 电脑桌面端 电脑端上的软件可以使用“图新地球”&#xff0c;直接把mbt文件拖到地图上即可&#xff0c;非常简便。 手机端 可以从华为应用商店下载“外业精灵”app…

Meta提出用向量检索来改进图像描述模型

出品人&#xff1a;Towhee 技术团队 作者&#xff1a;王翔宇 顾梦佳 随着深度神经网络的发展&#xff0c;自动图像描述技术取得了令人瞩目的进展。然而&#xff0c;现有的方法主要注重生成的描述与人类参考描述之间的相似性&#xff0c;却忽视了描述在实际应用中的特定目标。Me…

用户体验测试要怎么做?

用户体验测试是软件和应用程序开发的关键环节&#xff0c;它可以帮助团队了解如何优化产品以提高用户满意度&#xff0c;那用户体验测试要怎么做&#xff1f;下面是一些进行用户体验测试的最佳实践&#xff1a; 制定测试计划 在进行用户体验测试之前&#xff0c;您需要制定测试…

【Gradio】Could not create share link

Gradio是MIT的开源项目&#xff0c;用于构建机器学习和数据科学演示和 Web 应用&#xff0c;挺有意思的&#xff0c;感兴趣的同学可以去Gradio官网查看。 本地环境&#xff1a; - OS: Win 11 - Pyton: 3.10.11 - Gradio: 3.29.0 Gradio库的安装(Python 需要3.7 版本): - pip …

6.5 指令与文件的搜寻

6.5.1 指令文件名的搜寻 在终端机模式当中&#xff0c;连续输入两次[tab]按键就能够知道使用者有多少指令可以下达。 which &#xff08;寻找“可执行文件”&#xff09; 这个指令是根据“PATH”这个环境变量所规范的路径&#xff0c;去搜寻“可执行文件”的文件名。所以&…

iOS distribution发布证书过期或者被手动revoke了app会被下架吗?

在距离distribution 证书过期一个月&#xff08;或被手动revoke了&#xff09;的时候会受到apple的邮件 虽然distribution过期&#xff08;或者被手动revoke&#xff09;了&#xff0c;如果你的开发者账号是company&#xff08;公司&#xff09;类型或个人类型的&#xff0c;只…

LDR6023Q在USB摄像头转接器的应用

最近USB摄像头火了起来&#xff0c;连接手机后可以用于直播&#xff0c;内窥镜&#xff0c;探鱼器&#xff0c;上网课等等&#xff0c;应用非常广泛&#xff0c;但在长时间连接手机的时候&#xff0c;电量消耗特别快&#xff0c;所以USB摄像头转接器应运而生。 USB摄像头转接器…

多元回归预测 | Matlab海鸥算法(SOA)优化极限学习机ELM回归预测,SOA-ELM回归预测,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab海鸥算法(SOA)优化极限学习机ELM回归预测,SOA-ELM回归预测,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %% 清空环…

我的创作纪念日(一周年)

机缘 作为一位互联网安全专业的大一学生&#xff0c;我始终怀抱着提升自身技术能力、保护网络安全、推动互联网世界发展的初心。 通过实战项目的经验分享&#xff0c;我收获颇多。参与团队网络安全演练与攻防对抗&#xff0c;使我学会了应对不同类型攻击与漏洞&#xff0c;提…

vue(typescript)项目在vs中打开出现的各种问题

目录 vue3 报错解决&#xff1a;找不到模块或其相应的类型声明。&#xff08;Vue 3 can not find module&#xff09; (TS) 未知的编译器选项“allowImportingTsExtensions”。 TS6046 (TS) “--moduleResolution”选项的参数必须为 node, classic, node16, nodenext。…

FinalShell连接不上Ubantu

解决方法 1.ssh服务问题 1.先安装openssh-server服务 sudo apt install aopenssh-server 2.重启ssh服务 sudo systemctl restart ssh 2.防火墙问题 1. 直接关闭防火墙(最省时) ufw stop 2. 开放FinalShell要连接的端口号,下图。 ufw allow 22