Linux学习之进程三

news2024/9/20 15:24:21

目录

进程控制

fork函数

什么是写时拷贝

进程终止

mian函数的返回值

退出码

错误码

 exit()

进程等待

1.什么是进程等待?

2.为什么要进行进程等待?

3.如何进程进程等待?

wait,waitpid:

waitpid

进程替换

1.什么是进程程序替换?

execl

原理

其他exec族的函数

替换我们自己的程序


进程控制

fork函数

在此之前,我们基本已经了解到了fork函数,即用它来创建一个子进程,fork函数无参数,返回值类型pid_t,其次fork函数有两个返回值,在子进程中,返回0,创建失败返回-1,在父进程中,给父进程返回子进程的pid。即等于0,子进程,大于0,父进程,-1,创建失败。

这里重点提一下:写时拷贝

通常情况下,父子进程共享一份代码,父进程在不写入时,数据也是共享的,当一个进程要去往里面写入时,便以写时拷贝的方法各自一份副本。

如图,左边为父进程的代码与数据,子进程拷贝父进程的代码数据。右边为当子进程尝试要写入时,此时会重新申请空间,自己拷贝一份数据段副本,再往里面写,并且修改之前的映射关系。

什么是写时拷贝

其中再在父进程创建子进程时,会将自己的数据区(页表)读写权限改为只读,然后再创建子进程。这个过程用户是不知道的,而用户可能会去对某一批数据进行写入,而页表会因为权限此时会出错,操作系统此时会介入进行判断(是因为越界还是权限问题),重新申请内存写入,从而进行写时拷贝。

通过fork函数我们可以让子进程和父进程执行不同的代码,或者一个进程执行不同的程序。

进程终止

首先如何创建一个多进程呢?我们可以利用循环fork创建多个子进程,

 #include<stdio.h>
   #include<unistd.h>
   #include<stdlib.h>
   #define N 10
   
   typedef void (*callback_t)();
  void worker()
   {
     int num=10;
    while(num--)
    {
      printf("i am a child process,my pid is%d,ppid is%d,num:%d\n",getpid    (),getppid(),num);
     sleep(1);
   }
  }
  
  void creatsubproc(int n,callback_t cb)
  {
    int i;
    for(i=0;i<n;i++)
    {
      sleep(1);
      pid_t p=fork();
      if(p==0)
      {                                                                  
        //子进程的工作
        printf("creat %d chid process\n",i);
        cb();
        exit(0);
      }  
     }
  }
  int main()
  {
   creatsubproc(N,worker);//这里传入worker函数,即函数指针
   sleep(100);
   return 0;
  }

首先对于进程的运行,我们不关心,操作系统自己调度,我们创建的多进程,当子进程一个个运行完,就变成僵尸状态了。这里创建子进程时让自己才能恒运行自己worker时,我们可以单独写在外面,通过函数指针的方式,传参,这样以便于我们去修改进程数量以及worker内容。

这是我们再用ps指令监控我们的进程:

while :; do ps ajx| head -1&& ps ajx |grep myproc|grep -v grep;echo "---------------------------";sleep 1;done

 可以看到进程数量不断增多,且慢慢的从s->z状态,一个个终止了。

这里进程终止我们是通过调exit()函数,来实现进程终止,进程终止除了这种方式,也会正常终止,我们可以i用echo $?查看错误码。

对于进程终止,它的原理我们应该清楚,无非就是创建的pcb,页表等,终止时销毁了。可是对于进程终止我们需要了解它的应用:

mian函数的返回值

想了解进程终止的应用,首先我们先了解mian函数的返回值,首先我们知道我们一般写c/c++最后都是返回0,为什么呢?

首先当一个进程跑完,他的情况是怎样的呢?无非有三种情况:代码运行完毕,结果正确;代码运行完毕,结果不正确;代码异常终止;对于父进程,我们只看前两种。

在多进程环境中,父进程想知道子进程运行完结果是怎样的,而且该进程并没有任何的打印信息(我们人也无法观察得出),我们如何得知?

退出码

 对于main函数的返回值,其实就是一个退出码,0就表示的是运行成功-success,非0通常表示运行结果错误--failed,父进程通过子进程返回的退出码,知晓进程运行结果如何。

错误码

对于进程返回0,运行成功,没什么好说的,可是若是非0,运行不正确,但我们并不知道是哪里的问题,只知道进程运行有问题,那么是因为什么原因失败的?此时我们就需要错误码来告诉我们,因为非0表示错误,非0的数字有很多,我们就可以用他们表示不同的出错原因。

系统里会有一套字符串表示错误信息用来对应每个数字,通过错误码就可以知道运行结果及问题所在。而这个字符串错误信息就是strerror(c语言中的时候我们就接触过),我们通过一个循环来看看它的错误码对应的错误信息,并且有多少个:

int mian()
{
//我们也不知道多少,假定200
for(int i=0;i<200;i++)
{
  printf("%d:%s\n",i,strerror(i));
}
return 0;
}

 而在linux中,可以用$?查看上一次进程(指令)的错误码,?就如同环境变量一样,存放的是错误信息。

这些内置的错误码,如果没有有你想要的的错误说明,你也是可以自定义的,直接定义这样的字符数组

const char*err_string[]={"success","not find","write errorr"}

其下标就是对应的错误码。

对于我们的linux进程的异常,也是有对应的异常信息,对应的异常的错误码如下:

而我们的进程的错误也可以人工向他发出信号给进程从而实现异常:

 exit()

我们在上文知道了,进程直接退出可以调用exit(),那么关于exit到底是什么呢?早在学习c语言的时候,我们也许就接触过了,那么它实际是怎么用的呢?

 可以看到头文件和参数类型,这里的参数其实就是错误码(退出码),作用就是直接引起进程结束。

我们在意一段代码理解exit,进程退出:

 1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 int func()
  5 {
  6   printf("call func function done!\n");
  7   return 11;
  8 }
  9 int main()
 10 {
 11   func();//调用func函数
 12    printf("i am a process,pid: %d,ppid: %d\n",getpid(),getppid());
 13    //直接退出进程 exit(0);
 14    //对于main函数中直接return返回的是进程的错误码
 15    //其他函数return,仅代表该函数结束
 16    //return 21;
 17    //现在我们不用return 来返回退出码
 18    //直接调用exit对应的退出码。,其效果等价return                                                                                                     
 19    exit(21);
 20 }                                                
~                                                    
~                                                    
~               

我们在这里将他的退出码给21,此时我们运行程序后,在看他的错误码与return是一样的:

现在我们知道只有在main函数中的return 的是退出码,在子函数的return 只是代表返回该函数的返回值,退出该函数。当我们在其他函数中直接调用exit,此时就会直接退出进程,不会运行下面的代码了。

   #include<stdio.h>
   #include<unistd.h>
   #include<stdlib.h>
   int func()
  {
     printf("call func function done!\n");
     // return 11;
     exit(12);                                                                                                                                            
   }
  int main()
  {
    func();//调用func函数
     printf("i am a process,pid: %d,ppid: %d\n",getpid(),getppid());
     exit(21);
  }

 可以看到直接退出了进程,退出码为在这个子函数调用的exit的退出码。

除了手册3,man 3 exit 查看到的这个exit,还有man _exit,手册2的一个进程退出函数,用法基本相同,效果也差不多。

 它们有一个区别我们可以用一行代码体现:

printf("hello linu");
printf("hello linu\n");

首先对于exit,在终止进程时会刷新缓冲区,比如第一行代码,在没遇到\n之前,除非强制flush,否则只有等到进程终止此时才会想屏幕打印出来。

对于_exit,还是不加\n的时候。我们运行第一句话,这里没有/n,也没flush,此时进程终止,直接就退出来,什么也没打印,故此_exit终止不刷新缓冲区。

而所谓的缓冲区是绝对不在操作系统里,在我们的c库里,因此_exit与exit是存在这样的区别的。

进程等待

1.什么是进程等待?

首先进程等待就是通过wait/waitpid的方式,让父进程(一般)对子进程进行资源回收的等待过程

2.为什么要进行进程等待?

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

1.解决子进程僵尸问题带来的内存泄漏 ---必须的

2.子进程对于自己的任务完成的怎么样父进程需要知道---通过进程等待的方式获取子进程退出的信息(退出码与信号编号)。 

3.如何进程进程等待?

首先聊凭借两个接口:

wait,waitpid:

 对于wait,waitpid都是wait 2手册里的,调用这两个接口,需要包含头文件两个,参数分别为退出码;pid,退出码,选项。

wait的作用是可以帮父进程等待任意一个子进程的退出

waitpid:利用waitpid返回是否成功退出,返回pid退出成功,返回-1退出失败。

我们用一个例子来看看wait的作用:

void worker()
 {
    int cnt=5;
    while(cnt--)
   {
      printf("i am a process my pid: %d,my ppid:%d\n",getpid(),getppid())    ;
      sleep(1);
   }
  }
 
 int main()                                                             
 {
    pid_t id=fork();
    if(id==0)
   {
     worker();
     exit(0);
   }else{
      //father
      sleep(10);
     pid_t rid=wait(NULL);
     if(id==rid)
     {
      //等待成功
     printf("wait success,pid:%d\n",getpid()); 
     }
    sleep(10);
     }
       return 0;
}

 刚开始五秒,子进程在运行,五秒后遇到exit直接退出(异常退出),此时状态为僵尸状态,我们之前说过,僵尸进程是无法杀掉的,必须要让父进程接收到子进程的退出信息才行,之后继续运行父进程里的,先休眠10秒,做一下区分,然后父进程中,我们直接调用wait,退出码暂时不设置为null,调用wait之后,可以看到僵尸状态的子进程直接没了(子进程被成功回收),只有父进程,在10秒后,父进程结束。

总的概括就是,父进程通过调用wait函数,来获得子进程的退出信息,然后释放子进程,如果没调用wait,获取不到退出信息,此时进程就无法被释放。 

当我们在执行等待时,如果子进程根本就没有退出,父进程就必须在wait时进行阻塞等待,直到自己僵尸进程时候,wait就会自动回收,返回。也就是需要等待子进程运行完成为僵尸进程才能回收。

一般而言,谁先运行我们是不知道的,但是父进程都是最后退出的。

waitpid

wait只能等待当前的进程,没得选,而waitpid可以指定等待的进程。而对于waitdpid我们一般只需要掌握两种参数,首先对于Pid,指定等待进程的Pid,也可以设置为 -1,表示等待任意一个子进程。

参数int *status这里是一个输出型参数(通过函数把这个参数带出来给给操作系统),这里的option默认设置为0,表示的是阻塞等待。

我们将上述的测试再做修改:

 void worker()
 {
  int cnt=5;
  while(cnt--)
  {
 printf("i am a process my pid: %d,my ppid:%d--%d\n",getpid(),getppid(),cnt);
 sleep(1);
  }
 }
 
 int main()
 {
   pid_t id=fork();
  if(id==0)
 {
 worker();
 exit(10);
   }else{
      //father
     printf("wait before\n");
     int status=0;
    pid_t rid=waitpid(id,&status,0);
     if(id==rid)
    {
      //等待成功
      printf("wait success,pid:%d,%d\n",getpid(),status);
     }
     printf("wait after");                                                                                                                             
   }
  return 0;
 }

利用同等方式我们也可以实现进程等待,只不过对于这里的status,为什么时2560我们还是不得而知,实际上status是一个整数,一共32位,低16位:

首先我们在waitpid时不能对status整体使用,且我们可以通过等待完毕后的status的二进制分析得出他的退出码。

其次我们还可以通过有意8位在与上0xFF,得出信号位,用来表示是否收到信号。

对于这里的option除了0,一阻塞方式等待,还有一个状态NOHANG(宏),以非阻塞的方式的等待,

总结:

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

进程替换

1.什么是进程程序替换?

之前说过,父进程创建的子进程,子进程的代码和数据都是拷贝父进程的,可是我们如何让紫禁城区执行全新的任务呢,访问全新的数据,不在和父进程有瓜葛。且不是再去创建一个子进程。

这是我们需要程序替换:

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

我们以一个父进程为例

首先了解一下名来进程替换的接口:exec函数族

我们先看下一下execl函数的调用

#include<stdio.h>
   #include<unistd.h>
   int main()
   {
     //我们知道我们所使用的命令行指令都是一个个程序
     printf("pid:%d,exec command begin\n",getpid());
     execl("/usr/bin/ls","ls","-a","-l",NULL);
     printf("pid:%d,exec command end\n",getpid());
     return 0;                                                                                                                                           
  }


我们写一个简单的代码,可以看到如上我们是没有常见任何进程的,但是当我们运行之后

 可以看到我们直接调起了指令ls -a -l。但是后面的那一句话没有打印出来

以同样的方式,我们还调用了top指令。

 #include<stdio.h>
 #include<unistd.h>
  int main()
   {
     //我们知道我们所使用的命令行指令都是一个个程序
     printf("pid:%d,exec command begin\n",getpid());
     execl("/usr/bin/top","top",NULL);                                    
     printf("pid:%d,exec command end\n",getpid());
    return 0;
  }

 从上述的结果看出,我们可以通过语言调用其他程序。

execl

总结上述,那么通过execl函数可以调用其他程序。调用完之后,后面的代码不在运行。

 execl函数就是其中一个可以替换程序的函数:

对于它的参数,第一个path,表示表示替换程序所在路径+文件名,第二个 const char*arg   以及后面的......其实是可变参数列表,都是表示如何使用该指令。

在此之前我们学习到的命令行参数中参数就与这里的可变参数本质上就是同一个。

即第一个参数找到该程序,后面的参数如何执行该程序(与命令行保持一致)。

注意:无论如何去传递参数,末尾一定是以NULL结尾,表示参数传递完毕!

原理

第二个问题,为什么执行完调用的程序,后面没在执行了?

实际上execl执行完就已经完成了程序的替换,我们知道在mm_struct中管理着进程的内存空间:

直接替换源程序的数据与代码,我们源程序的pid等其他属性不变,此时在这个过程中,不用产生新的进程就完成了进程的程序替换。

此时我们再通过多进程再来感受一下进程替换:

 #include<stdio.h>
   #include<unistd.h>
   #include<sys/types.h>
   #include<sys/wait.h>
   int main()
   {
     pid_t id=fork();
     if(id==0)
     {
     //子进程     
   printf("pid:%d,exec command begin\n",getpid());
   sleep(1);
    execl("/usr/bin/ls","ls","-a","-l",NULL);
    printf("pid:%d,exec command end\n",getpid());
   }else{                                                               
     //父进程
     pid_t rid=waitpid(-1,NULL,0);//父进程来等待子进程
    if (rid>0)
       {
         printf("wait succees rid:%d\n",rid);
       }
    }
 return 0;
 }

 看到结果首先pid是没有变化的,其次还是没有看到command end这句话。

创建子进程的时候,之前我们就说过,子进程与父进程数据共享,代码以写时拷贝的方法各自私有一份,在子进程发生替换时,通过写时拷贝(代码段与数据都重新拷贝),以保持父子的独立性,父进程与子进程不会相互影响。

现在就说一说子进程在替换后,是如何知道我们的代码段该从哪里运行,其次为什么的后面的代码不再执行了?

这个我们之前也提到过(fork创建子进程时运行代码段),其实就是程序计数器,pc指针与 eip会记录函数在运行的过程中,执行到哪一步了,在没替换之前,被记录下来,所以知道从哪里运行,其次,无论是多进程还是单进程,进程替换之后,代码都被替换,eip重新从新的程序开始,下面的代码就不会再执行了,最后被回收。

其他exec族的函数

了解了替换的本质后,我们再来看看exec族的其他函数:

 第一个参数与execl不一样,这里表示的是文件名,即这里的execlp,p指的是path,我们不需要给他完整的路径,他自己会去寻找,给出文件名即可。

  int main()
  {
    printf("pid:%d,exec command begin\n",getpid());
    execlp("ls","ls","-a","-l",NULL);                                    
    printf("pid:%d,exec command end\n",getpid());
    return 0;                                    
  }        

 

再看看exev,这里的vector表示的是vector,技术组,可以看到这里的第二个参数由可变参数变成了字符串数组传参,与我们之前说的命令行参数的形式一样,这里再传入参数时,提前将命令行参数传入数组:

int main()
    {
      printf("pid:%d,exec command begin\n",getpid());
      char*const argv[]={"ls","-a","-l",NULL};                          
      execv("/usr/bin/ls",argv);
      printf("pid:%d,exec command end\n",getpid());
      return 0;
    }

execvp可以看到这里就是与execv的区别就是不用完整路径(也可以用完整路径)。

int main()
    {
      printf("pid:%d,exec command begin\n",getpid());
      char*const argv[]={"ls","-a","-l",NULL};                          
      execv("ls",argv);
      printf("pid:%d,exec command end\n",getpid());
      return 0;
    }

替换我们自己的程序

基本掌握了程序替换,对于上述我们都是举例外壳程序的指令,当然我们是可以替换自己的程序:

我们写一个简单的c++代码--打印hello c++,编译形成可执行,通过execl调用我们的程序:

int mian()
{
execl("./myprocc","myprocc",NULL);
return 0;
}                                   

 

当然除了c++,python,汇编程序,其他脚本语言都可以替换,调用。

 

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

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

相关文章

【Git】Gui图形化管理、SSH协议私库集成IDEA使用

一、Gui图形化界面使用 1、根据自己需求打开管理器 2、克隆现有的库 3、图形化界面介绍 1、首先在本地仓库更新一个代码文件&#xff0c;进行使用&#xff1a; 2、进入图形管理界面刷新代码资源&#xff1a; 3、点击Stage changed 跟踪文件&#xff0c;将文件处于暂存区 4、通过…

基于JavaWeb+SpringBoot+Vue摩托车商城微信小程序系统的设计和实现

基于JavaWebSpringBootVue摩托车商城微信小程序系统的设计和实现 源码传送入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码传送入口 前言 近年来&#xff0c;随着移动互联网的快速发展&#xff0c;电子商务越来越受到…

javascript用localStorage存储用户搜索词记录,并在搜索框下展显搜索词记录

//首先是storage的一封装 //storage.js文件 function storage(){//设置storage密钥this.ms"mystorage";}//以下为函数的原型方法//获得localStorage值storage.prototype.getLocalfunction(key){//先检查设置的localStorage的密钥var mydatalocalStorage.getItem(thi…

问题描述:64位计算机的寻址能力是多少TB

问题描述&#xff1a;64位计算机的寻址能力是多少TB 我在看到一个32位电脑的寻址能力计算时&#xff0c;看到是这么计算的。 虚拟内存的大小受到计算机地址位数的限制&#xff0c; 那么32位电脑的寻址能力计算应该是这样 为什么网上百度到的是16TB呢&#xff0c;如下图所示 中…

数据库安全:Hadoop 未授权访问-命令执行漏洞.

数据库安全&#xff1a;Hadoop 未授权访问-命令执行漏洞. Hadoop 未授权访问主要是因为 Hadoop YARN 资源管理系统配置不当&#xff0c;导致可以未经授权进行访问&#xff0c;从而被攻击者恶意利用。攻击者无需认证即可通过 RESTAPI 部署任务来执行任意指令&#xff0c;最终完…

winform打包默认安装路径设置

点击安装程序的 Application Folder 修改属性中的 DefaultLocation

高校教务系统登录页面JS分析——长沙理工大学教务系统

高校教务系统密码加密逻辑及JS逆向 本文将介绍高校教务系统的密码加密逻辑以及使用JavaScript进行逆向分析的过程。通过本文&#xff0c;你将了解到密码加密的基本概念、常用加密算法以及如何通过逆向分析来破解密码。 本文将是本专栏最后一篇文章&#xff0c;我看了绝大多数高…

【编程语言发展史】Go语言的发展历史

目录 Go的起源 Go语言发展时间轴 logo Go的起源 Go 语言起源 2007 年&#xff0c;并于 2009 年正式对外发布。它从 2009 年 9 月 21 日开始作为谷歌公司 20% 兼职项目&#xff0c;即相关员工利用 20% 的空余时间来参与 Go 语言的研发工作。该项目的三位领导者均是著名的 …

1. 深度学习——激活函数

机器学习面试题汇总与解析——激活函数 本章讲解知识点 什么是激活函数&#xff1f; 为什么要使用激活函数&#xff1f; 详细讲解激活函数 本专栏适合于Python已经入门的学生或人士&#xff0c;有一定的编程基础。本专栏适合于算法工程师、机器学习、图像处理求职的学生或人…

CAN轴【禾川】

禾川CAN轴有问题。 厂家说是只能使用禾川的伺服X2EN&#xff0c;和X3EN 添加CAN主站&#xff1a; 网络&#xff1a; 0 波特率&#xff1a; 1000K 添加CAN总线&#xff1a; 主站&#xff1a; 2 同步帧&#xff1a; 80h 设置刷新时间 时间帧&#xff1a;100h 添加伺服&…

野火i.MX6ULL开发板wifi连接、SHH登录玄学篇

1、WiFi连接成功 服了&#xff0c;一样的步骤&#xff0c;它又行了。 手机开热点&#xff0c;2.4G频段&#xff0c;wanghaha&#xff0c;连上显示了IP地址&#xff0c;输入ping 百度网址 等了七八秒它访问成功。 中间还用过usb线刷镜像Debian。 2、使用 MobaXterm SSH 登录…

页表和cache

页表基本原理 页表主要用来将虚拟地址映射到物理地址&#xff0c;在使用虚拟地址访问内存时&#xff0c;微处理器首先将虚拟地址拆分成页号和页内偏移量&#xff0c;然后使用页号在页表中查找对应的物理页框号&#xff0c;将物理页地址加上页内偏移量&#xff0c;得到最终的物…

skynet学习笔记02— skynet介绍、skynet基础API与环境变量

01、Skynet与Actor模型 在系统Skynet之前&#xff0c;先了解一下Skynet与Actor模型&#xff0c;下列是风云大佬的介绍以及一个大佬的博客 https://github.com/cloudwu/skynet/wiki/GettingStartedhttps://blog.csdn.net/qq769651718/article/details/79432793 02、Skynet基础…

3.前端调式(断点调式)

1. Elements 先来看这张图最上头的一行是一个功能菜单&#xff0c;每一个菜单都有它相应的功能和使用方法&#xff0c;依次从左往右来看 箭头按钮 用于在页面选择一个元素来审查和查看它的相关信息&#xff0c;当我们在Elements这个按钮页面下点击某个Dom元素时&#xff0c;箭…

【全网首发】【Python】Python控制parrot ARDrone 2.0无人机

&#x1f389;欢迎来到Python专栏~Python控制parrot ARDrone 2.0无人机 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;Python学习专栏 文章作者技术和水平有限&#xff0c;如果文中出现错误…

Zotero详细功能补充!熟练使用!【进阶版,持续更新】

Zotero安装请参见文章Zotero安装 1.改变条目文件夹 如果直接选择条目直接进行移动&#xff0c;能移动成功&#xff0c;但是原来文件夹和目标文件夹都会存在&#xff0c;实际是复制&#xff01; 如果只想保留在一个文件夹里面&#xff0c;可以选中条目&#xff0c;右击-从分…

11.10

.text .global _start _start: 1.RCC时钟使能GPIOE RCC_MP_AHB4ENSETR[4]->1 LDR R0,0x50000a28 LDR R1,[R0] ORR R1,R1,#(0x3<<4) ORR R1,R1,#(0x1<<1) STR R1,[R0] 2.设置PE10为输出模式 GPIOE_MODER[21:20]->01 先清0 LDR R0,0x50006000 LDR R1,[R0]…

探索云世界的无限可能

文章目录 每日一句正能量前言云计算的定义和现状云计算能做什么&#xff1f;云计算市场的新特征需求方向&#xff1a;云计算的基础服务已经稳固&#xff0c;行业解决方案是新的发力点模式方向&#xff1a;分布式云模式方向&#xff1a;边缘计算是一朵新的云技术方向&#xff1a…

Bengio担任一作,联手一众图灵奖得主,预防AI失控,扛起AI监管大旗

图灵奖得主最近都在关心些什么呢&#xff1f;Yoshua Bengio&#xff0c;深度学习的奠基人之一&#xff0c;前几天他担任一作&#xff0c;联合多位大佬&#xff0c;发文探讨了如何在人工智能&#xff08;AI&#xff09;快速发展的时代管控相关风险&#xff0c;共同寻求当下生成式…

LeetCode(1)合并两个有序数组【数组/字符串】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 88. 合并两个有序数组 1.题目 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合…