Linux的进程控制

news2024/7/4 5:43:17

进程创建后,需要对其进行合理管理,光靠OS 是无法满足我们的需求的,此时可以运用进程控制相关知识,对进程进行手动管理,如创建进程、终止进程、等待 进程等,其中等待进程可以有效解决僵尸进程问题。

1、进程创建

1.1、fork函数

#include <unistd.h> //所需头文件
pid_t fork(void);   //fork 函数

看如下代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> //进程等待相关函数头文件
​
int main()
{
  //创建两个子进程
  pid_t id1 = fork();
  if(id1 == 0)
  {
    //子进程创建成功,创建孙子进程
    pid_t id2 = fork();
    if(id2 == 0)
    {
      printf("我是孙子进程,PID:%d   PPID:%d\n", getpid(), getppid());
      exit(1); //孙子进程运行结束后,退出
    }
​
      wait(0);  //等待孙子进程运行结束
      printf("我是子进程,PID:%d   PPID:%d\n", getpid(), getppid());
      exit(1);  //子进程运行结束后,退出
  }
​
  wait(0);  //等待子进程运行结束
  printf("我是父进程,PID:%d   PPID:%d\n", getpid(), getppid());
​
  return 0; //父进程运行结束后,退出
}
​

可以得出结论:两个子进程已经创建成功,但最晚创建的进程总是最先运行。先执行的哪个进程取决于调度器

1.2 写时拷贝

写时拷贝机制实现原理就是通过页表+MMU机制,对不同进程进行空间寻址,达到出现改写行为时,父子进程使用不同空间地址的效果。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> //进程等待相关函数头文件
​
const char* ps = "This is a Beauty(美女)";  //全局属性
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    ps = "This is a Beast(野兽)"; //改写
    printf("我是子进程,我认为:%s\n", ps);
    exit(0);  //子进程退出
  }
​
  wait(0);  //等待子进程退出
  printf("我是父进程,我认为:%s\n", ps);
  return 0;
}
​

运行结果:

可以得出结论:

子进程对ps指针做出改变,父进程不受影响。这就是写时拷贝机制。

2、进程终止

常见的进程退出方式:

1、代码跑完,结果正确

2、代码跑完,结果错误

3、代码没跑完,程序异常了

2.1进程退出码

我们把 main 函数的 return x 返回值称之为 进程退出码

进程退出码是非常重要的,进程退出码表征了进程推出的信息,它是要给父进程读取的。

我们通过内置命令 echo,我们让 \textrm{bash} 自己执行内部的函数来打印。

$ echo $?

看如下代码:

表示代码运行过后result不为100,所以return 11。

2.2 进程错误码

首先,失败的非零值是可以自定义的,我们可以看看系统对于不同数字默认的 错误码 是什么含义。C 语言当中有个的 string.h``中有一个 strerror 接口,是最经典的、将错误码表述打印出来的接口,

代码:

#include <stdio.h>
#include <string.h>
 
int main(void) 
{
  int i = 0;
  for (i = 0; i < 1000; i++) {
    printf("%d: %s\n", i, strerror(i));
  }
}

运行结果:

一共有133个错误码,错误码退出码可以对应不同的错误原因,方便我们定位问题出在哪里。

2.3退出方式

对一个正在运行中的进程,存在两种终止方式:外部终止和内部终止,外部终止时,通过 kill -9 PID 指令,强行终止正在运行中的程序,或者通过 ctrl + c 终止前台运行中的程序

内部终止是通过函数 exit()_exit() 实现的 之前在程序编写时,发生错误行为时,可以通过 exit(-1) 的方式结束程序运行,代码中任意地方调用此函数,都可以提前终止程序

void exit(int status);
void _exit(int status);

来看看exit()与_exit()的区别:

使用 _exit() 时,并没有任何语句输出

3、进程等待

3.1为什么要进程等待

子进程运行结束后,父进程没有等待并接收其退出码和退出状态,OS无法释放对应的内核数据结构+代码数据,出现僵尸进程。

为了避免这种情况的出现,父进程可以通过函数等待子进程的运行结束,此时父进程属于阻塞状态。进程等待也可以获取子进程的执行结果。

3.2 等待函数

系统提供的父进程等待函数有两个 wait()waitpid(),后者比较常用

#include <sys/types.h>
#include <sys/wait.h>
​
pid_t wait(int* status);
​
pid_t waitpid(pid_t pid, int* status, int options);

status 位图结构

wait 和 waitpid,都有一个 status 参数,该参数是一个输出型参数,由操作系统填充;

如果传递NULL,表示不关心子进程的退出状态信息;否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程;

status 不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究 status 低16比特位):

可以看到,status 低两个字节的内容被分成了两部分 – 第一个字节前七位表示退出信号,最后一位表示 core dump 标志;第二个字节表示退出状态,退出状态即代表进程退出时的退出码;

对于正常退出的程序来说,退出信号和 core dump 标志都为0,退出状态等于退出码;对于异常终止的程序来说,退出信号为不同终止原因对应的数字,退出状态未用,无意义。

wait函数的使用

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
    int id=fork();
    if(id == 0) {  //子进程
        int cnt = 5;
        while(cnt--) {
            printf("子进程, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt);
            sleep(1);
        }
        exit(1);
       }
     else {  //父进程
        sleep(10);
        int status = 0;
        pid_t ret = wait(&status);
        printf("exit code:%d\n",status);
     }
     return 0;
}

运行结果可知:可以看到,最开始父子进程都处于睡眠状态 S,之后子进程运行5s退出,此时由于父进程还要休眠5s,所以没有对子进程进行进程等待,所以子进程变成僵尸状态 D;5s过后,父进程使用 wait 系统调用对子进程进行进程等待,所以子进程由僵尸状态变为彻底死亡状态。

waitpid函数使用

我们也可以用 waitpid 来进行进程等待:

头文件:sys/types.h  sys/wait.h
    
函数原型:pid_t waitpid(pid_t pid, int *status, int options);
​
pid:Pid=-1,等待任意一个子进程,与wait等效;Pid>0.等待其进程id与pid相等的子进程;
    
status:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL;
    
options:等待方式,options=0,阻塞等待;options=WNOHANG,非阻塞等待;
    
返回值:waitpid调用成功时返回被等待进程的pid;如果设置了WNOHANG,且waitpid发现没有已退出的子进程可收集,则返回0;调用失败则返回-1;
​

waitpid代码举例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
    int id=fork();
    if(id == 0) {  //子进程
        int cnt = 5;
        while(cnt--) {
            printf("子进程, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt);
            sleep(1);
        }
        exit(1);
       }
     else {  //父进程
        sleep(10);
        int status = 0;
        pid_t ret = waitpid(id,&status,0);
        printf("exit signal:%d,  exit code:%d\n", (status & 0x7f), (status >> 8 & 0xff));
     }
     return 0;
}
printf("exit signal:%d,  exit code:%d \n", (status & 0x7f), (status >> 8 & 0xff)); 

其中,status 按位与上 0x7f 表示保留低七位,其余九位全部置为0,从而得到退出信号;

status 右移8位得到退出状态,再按位与上 0xff 是为了防止右移时高位补1的情况;

可以看到,waitpid 和 wait 还是有很大区别的 – waitpid 可以传递 id 来指定等待特定的子进程,也可以指定 options 来指明等待方式。

阻塞与非阻塞等待

waitpid 函数的第三个参数用于指定父进程的等待方式:

阻塞:顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回。 非阻塞:就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则持续返回一个值来告知事件未发生,进程或线程继续执行,直到事件发生才为最后一次返回。

WIFEXITED 与 WEXITSTATUS 宏

Linux 提供了 WIFEXITED 和 WEXITSTATUS 宏 来帮助我们获取 status 中的退出状态和退出信号,而不用我们自己去按位操作:

WIFEXITED (status):若子进程正常退出,返回真,否则返回假;(查看进程是否是正常退出)(wait if exited)
WEXITSTATUS (status):若 WIFEXITED 为真,提取子进程的退出状态;(查看进程的退出码)(wait exit status)
if(WIFEXITED(status)) {  //正常退出
    printf("exit code:%d\n", WEXITSTATUS(status));
} else {  //异常终止
    printf("exit signal:%d\n",WIFEXITED(status));
}

非阻塞等待与宏的代码展示:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
​
void task1() {
    printf("task is running...\n");
}
​
void task2() {
    printf("task is runnning...\n");
}
​
int main() {
    int id = fork();
    if(id == 0) {  //子进程
        int cnt = 5;
        while(cnt--) {
            printf("子进程, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt);
            sleep(1);
        }
        exit(1);
    } else {  //父进程
        int status = 0;
        while(1) {  //轮询
            pid_t ret = waitpid(id, &status, WNOHANG);  //非阻塞式等待
            if(ret == 0){  //调用成功,但子进程未退出
                printf("等待成功,但子进程未退出\n");
                task1();  //执行其他命令
                task2();
            } else {  //调用成功,子进程退出
                printf("等待成功,子进程退出\n");
                break;
            }
            sleep(1);
        }
​
        if(WIFEXITED(status)) {  //正常退出
            printf("exit code:%d\n", WEXITSTATUS(status));
        } else {  //异常终止
            printf("exit signal:%d\n",WIFEXITED(status));
        }
        
    }
    return 0;
}

代码展示结果可以看出:程序正常运行,使用waitpid的非阻塞等待,父进程通过等待轮询的方式,父进程在等待子进程的过程中,也可以执行其他任务。

4、进程程序替换

在上面进程创建中我们提到,fork 函数一般有两种用途 – 创建子进程来执行父进程的部分代码以及创建子进程来执行不同的程序,创建子进程来执行不同的程序就是进程程序替换。如果想让子进程和父进程彻底分开,让子进程彻彻底底地执行一个全新的程序,我们就需要 进程的程序替换

为什么要进行程序替换?因为我们想让我们的子进程执行一个全新的程序。

那为什么要让子进程执行新的程序呢?

我们一般在服务器设计的时候(Linux 编程)的时候,往往需要子进程干两件种类的事情:

  • 让子进程执行父进程的代码片段(服务器代码…)

  • 想让子进程执行磁盘中一个全新的程序(shell、想让客户端执行对应的程序、通过我们的进程执行其他人写的进程代码、C/C++ 程序调用别人写的 C/C++/Python/Shell/Php/Java...)

5、七大进程替换函数

进程替换函数一共有七个,其中六个都在调用execve。execve才是真正的系统级接口

这些函数都属于exec家族的替换函数,这些函数在程序替换失败后才有返回值,返回值为-1。程序都已经替换成功,后续代码也都将被替换,所以成功后的返回值也就没意义了

5.1函数execl

#include<unistd.h>
int execl(const char* path, const char& arg, ...);

如果我们想执行一个全新的程序,我们需要做几件事情:

(要执行一个全新的程序,以我们目前的认识,程序的本质就是磁盘上的文件)

  • 第一件事情:先找到这个程序在哪里。

  • 第二件事情:程序可能携带选项进行执行(也可以不携带)。

明确告诉 OS,我想怎么执行这个程序?要不要带选项。

\Rightarrow 简单来说就是:① 程序在哪? ② 怎么执行?

所以,execl的接口就把这两个功能给体现出来了

  • 它的第一个参数是 path,属于路径。

  • 参数 const char* arg, ... 中的 ... 表示可变参数,命令行怎么写(ls, -l, -a) 这个参数就怎么填。ls, -l, -a 最后必须以 NULL 结尾,表示 "如何执行程序的" 参数传递完毕。

代码展示:

#include<stdio.h>
#include<unistd.h>
int main(){
    printf("我是一个进程,我的PID是:%d\n", getpid());
    // ls -a -l
    int ret=execl("/usr/bin/ls", "ls", "-l", "-a", NULL);  // 带选项
    //程序替换多发生于子进程,也可以通过子进程的退出码来判断是否替换成功
    if(ret == -1)
    printf("程序替换失败!\n");
 
    printf("我执行完毕了,我的PID是:%d\n", getpid());
 
    return 0;
}

结果展示:

可以看出:函数execl中的命令+选项+NULL是以链表的方式进行传递的。

5.2函数execv

#include <unistd.h>
int execv(const char* path, char* const argv[]);

path 参数和 execl 一样,关注的都是 "如何找到"

argv[] 参数关注的是 "如何执行",是个指针数组,放 char* 类型,指向一个个字符串。

大家在命令行上 $ ls -a -l ,在 execl 里我们是这么传的: "ls", "-a", "-l", NULL 。

所以 execv 和 execl 只有传参方式的区别,一个是可变参数列表 (l),一个是指针数组 (v)。

  • 返回值:替换失败返回 -1

  • 参数1:待替换程序的路径,如 /usr/bin/ls

  • 参数2:待替换程序名及其命名构成的 指针数组,相当于一张表

代码展示:

#include<stdio.h>
#include<unistd.h>
int main(){
    printf("我是一个进程,我的PID是:%d\n", getpid());
    // ls -a -l
    char* const argv[]={
        "ls","-a","-l",NULL
    };//arvg表,实际为指针数组
   
    execv("/usr/bin/ls",argv);
 
    printf("我执行完毕了,我的PID是:%d\n", getpid());
 
    return 0;
}

可以看出:展示结果和之前一样。

5.3函数execlp

可能写path路径过于麻烦了,我们可以换成写文件名,比如说写ls,它就会自动帮我们找到对应的路径。所以这一块的参数传递,和 execl 是一样的,唯一的区别是比 execl 多了一个 p

我们执行指令的时候,默认的搜索路径在环境变量 \textrm{PATH} 中,所以这个 p 的意思是环境变量。

这意味着:执行 execlp 时,会直接在环境变量中找,不用去输路径了,只要程序名即可。

#include <unistd.h>
int execlp(const char* file, const char* arg, ...);
  • 返回值:替换失败返回 -1

  • 参数1:待替换程序名,如 lspwdclear

  • 参数2~N:可变参数列表,为命令的选项

代码展示:

#include <stdio.h>
#include <stdlib.h> //exit 函数头文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
    //execlp函数
    printf("我是父进程,我的PID是: %d\n", getpid());
    pid_t id=fork();
    if(id==0){
        printf("我是子进程,我的PID是: %d\n",getpid());
        execlp("ls","ls","-a","-l",NULL);//程序替换
        printf("还可以调用吗?");
        exit(-1);
    }
  int status = 0;
  waitpid(id, &status, 0);  //等待阻塞
  if(WIFEXITED(status)) {  //正常退出
    printf("子进程替换成功,exit code:%d\n", WEXITSTATUS(status));
} 
else {  //异常终止
    printf("子进程异常终止,exit signal:%d\n",WIFEXITED(status));
}
​
  return 0;
}

结果展示:

程序替换在子进程替换只会影响调用的子进程,不会影响父进程。

5.4函数execvp

int execvp(const char* file, char* const argv[]);

代码展示:

#include <stdio.h>
#include <stdlib.h> //exit 函数头文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
    //execlp函数
    printf("我是父进程,我的PID是: %d\n", getpid());
    pid_t id=fork();
    if(id==0){
         char* const argv[] = 
    {
      "ls",
      "-a",
      "-l",
      NULL
    };
        printf("我是子进程,我的PID是: %d\n",getpid());
        execvp("ls",argv);//程序替换
        printf("还可以调用吗?");
        exit(-1);
    }
  int status = 0;
  waitpid(id, &status, 0);  //等待阻塞
  if(WIFEXITED(status)) {  //正常退出
    printf("子进程替换成功,exit code:%d\n", WEXITSTATUS(status));
} 
else {  //异常终止
    printf("子进程异常终止,exit signal:%d\n",WIFEXITED(status));
}
​
  return 0;
}

5.5函数execle

e表示env环境变量表,可以将自定义或当前程序中的环境变量表传给待替换程序。

#include <unistd.h>
int execl(const char* path, const char* arg, ..., char* const envp[]);
  • 最后一个参数:替换成功后,待替换程序的环境变量表,可以自定义

makefile知识点补充:

我们前几章写的 Makefile 文件只能形成一个可执行程序,现在我们学习如何形成多个。

比如,如果我们想一口气形成 2 个 可执行程序:

假设有两个可执行程序:mycmd.cpp & mytest.c,我们期望用 mytest.c 调用 mycmp.cpp:

也就是 C 语言的可执行程序调用 C++ 的可执行程序,我们先来设计一下 Makefile。

我们需要在前面添加 .PHONY:all ,让伪目标 all 依赖 mytest 和 mycmd。

如果不这样做,直接写,默认生成的是 mycmd,轮不到后面的 mytest,属于 "先到先得"。

且 Makefile 默认也只能形成一个可执行程序,想要形成多个就需要用到 all 了。

.PHONY:all
all:mytest mycmd
 
mytest:mytest,c
  gcc -o $@ $^
mycmd:mycmd.cpp
  g++ -o $@ $^
.PHONY:clean
clean:
  rm -f mytest mycmd

先写一个cpp的代码:

 #include<iostream>
 using namespace std;
 extern char** environ;  //声明环境变量表                                                                                                                                      int main(){
  int pos = 0;
    //只打印5条
     while(environ[pos] && pos < 5)
     {
      cout << environ[pos++] << endl;
   }
    return 0;
 }

编译结束后看它所在的路径:

再编写用进程函数替换execle代码:

#include <stdio.h>
#include <stdlib.h> //exit 函数头文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
    //execlp函数
    extern char** environ;     // 环境变量的指针声明
    printf("我是父进程,我的PID是: %d\n", getpid());
    pid_t id=fork();
    if(id==0){
        printf("我是子进程,我的PID是: %d\n",getpid());
        execle("./mycmd","mycmd", NULL, environ);//程序替换
        printf("还可以调用吗?");
        exit(-1);
    }
  int status = 0;
  waitpid(id, &status, 0);  //等待阻塞
  if(WIFEXITED(status)) {  //正常退出
    printf("子进程替换成功,exit code:%d\n", WEXITSTATUS(status));
} 
else {  //异常终止
    printf("子进程异常终止,exit signal:%d\n",WIFEXITED(status));
}
​
  return 0;
}

这不就可以成功替换了:

也可以使用自定义的环境变量:

5.6函数execve

#include <unistd.h>
int execve(const char* filename, char* const argv[], char* const envp[]);
​

execle 参数传递是参数列表,execve 参数传递是数组,仅此而已。

5.7函数execvpe

#include <unistd.h>
int execvpe(const char* file, char* const argv[], char* const envp[]);
  • 返回值:替换失败返回 -1

  • 参数1:待替换程序名,需要位于 PATH

  • 参数2:待替换程序名及其命名构成的 指针数组

  • 参数3:传递给待替换程序的环境变量表

最后分享一波记忆方法:

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

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

相关文章

日撸 Java 三百行day38

文章目录 说明day381.Dijkstra 算法思路分析2.Prim 算法思路分析3.对比4.代码 说明 闵老师的文章链接&#xff1a; 日撸 Java 三百行&#xff08;总述&#xff09;_minfanphd的博客-CSDN博客 自己也把手敲的代码放在了github上维护&#xff1a;https://github.com/fulisha-ok/…

java--Lock锁

1.概述 锁是一种工具&#xff0c;用于控制对共享资源的访问 Lock和synchronized&#xff0c;这两个是最常见的锁&#xff0c;它们都可以达到线程安全的目的&#xff0c;但是在使用上和功能上又有较大的不同。 Lock并不是用来代替synchronized的&#xff0c;而是当使用synchroni…

verilog手撕代码3——序列检测和序列发生器

文章目录 前言一、序列检测器1.1 重复序列检测1.1.1 序列缓存对比/移位寄存器法1.1.2 状态机法 1.2 非重复序列检测 二、序列发生器2.1 移位寄存器法2.2 反馈法2.3 计数器法 前言 2023.4.25 2023.4.26 学习打卡&#xff0c;天气转晴 一、序列检测器 1.1 重复序列检测 1.1.1 …

SpringBoot整合EasyExcel上传下载前后端

SpringBoot整合EasyExcel上传下载前后端 需求&#xff0c;在项目启动时加载表格里的数据初始化&#xff0c;前端可以上传全部部门的表格数据&#xff0c;后台根据部门名字解析归类数据和根据表格的部门下载部门数据1.后端1.1创建一个SpringBoot项目&#xff0c;引入依赖1.2 在r…

推荐几个可以免费体验GPT-4的网站

想要体验GPT-4除了每月花20美刀还有别的办法吗&#xff1f;&#xff08;甚至现在有钱都花不了&#xff09; 问就是有的&#xff0c;我搜罗了一些可以免费使用GPT-4的网站&#xff08;注意需要魔法&#xff09;&#xff0c;体验之后觉得还行&#xff0c;推荐给大家。 有别的大…

JavaWeb学习笔记

文章目录 一. HTML二. CSS三. JavaScript1. 引入2.语法/输出语句3. 变量/数据类型4. 运算符5. 流程控制语句6. 函数7. 对象8. 事件监听 四. Servlet1.执行流程2. 生命周期3. 常用方法4. 体系结构5. 配置Servlet 五. JSP1. 简介2. JSP原理3.脚本4.JSP缺点5. EL表达式6. JSTL标签…

AI+HPC?人工智能高性能计算方向就业新路子

刚刚过去的3月&#xff0c;GPT-4刷屏了。吃瓜群众一边津津乐道&#xff0c;一边瑟瑟发抖。随后国产大模型紧随其后&#xff0c;百度的“文心一言”、阿里的“通义千问”、复旦大学的“MOSS”、商汤的“商量”竞赛般的亮家伙&#xff0c;有点全民练模型&#xff0c;人人GPT的味道…

【最新】Jetson Agx Xavier烧录环境到TensorRT加速(高集成,快速简单有效)

一.下载烧录好的基础镜像 1. 基础环境 当前镜像包是ubuntu18.08,镜像。镜像包已安装jetpack 4.6,python3.6 &#xff0c;torch1.7, opencv, tensorrt等&#xff0c;运行模型的基本环境都已搭建。jetpack 是4.6 对应L4T是32.6.1。如下图&#xff1a; (1).下载当前文件包&…

OSCP-Escape(gif绕过)

目录 扫描 WEB 扫描 sudo nmap 192.168.233.113 -p- -sS -sVPORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) 80/tcp open http Apache httpd 2.4.29 ((Ubuntu)) 8080/tcp open http Apache…

Golang中的一些关键字(defer、:=、go func())

作者&#xff1a;非妃是公主 专栏&#xff1a;《Golang》 博客主页&#xff1a;https://blog.csdn.net/myf_666 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录 defervar与 : 的区别var:二者区别 go func de…

antDesignPro6项目:供应链系统—实战问题解决汇总

系统使用的技术&#xff1a;antDesignPro6 Umi4 antDesign antDesignProComponents 其他技术 1、如何设置ModalForm组件&#xff0c;销毁时&#xff0c;自动重置表单&#xff1f; modalProps{{ destroyOnClose: true }} // 重置表单 答&#xff1a;给ModalForm组件添加mo…

智加科技+舍弗勒,首发量产正向开发的智能重卡冗余转向

对于自动驾驶赛道来说&#xff0c;感知、规划和控制&#xff0c;除了计算平台、算法等核心上层软硬件支持&#xff0c;底盘控制系统同样是关键一环。事实上&#xff0c;从Demo到规模化量产&#xff0c;更好的车身控制能力以及冗余备份&#xff0c;也是自动驾驶公司迈入2.0阶段的…

中介作用分析流程

中介作用分析流程 一、案例背景 家庭氛围对于学生成长具有重要的意义。好的家庭氛围能够增强学生的家庭幸福感&#xff0c;促进学生良好性格的形成。在这个过程中&#xff0c;父子沟通与母子沟通对于家庭氛围和家庭幸福感都具有显著影响作用。现在有一项研究想要探究父子沟通和…

企业级信息系统开发讲课笔记3.2 基于Java配置类整合SSM框架实现用户登录

文章目录 零、本节学习目标一、采用MVC架构二、用户登录运行效果三、基于Java配置类整合SSM框架实现用户登录&#xff08;一&#xff09;创建数据库与表1、创建数据库2、创建用户表3、在用户表里插入记录 &#xff08;二&#xff09;创建Maven项目&#xff08;三&#xff09;添…

带你打开GCC的大门

START hi&#xff0c;大家好&#xff01; 今天带大家了解一下GCC。 首先说一句&#xff1a;大写的GCC和小写的gcc不是一个东西呦&#xff0c;下面我们慢慢道来... 1. GCC是什么&#xff1f; GNU Compiler Collection (GCC)是GNU项目开发的编译工具集&#xff0c;支持各种编…

abaqus和ansys做仿真哪个更好

当你要模拟仿真一个机械模型时&#xff0c;通常会听到ABAQUS或ANSYS&#xff0c;最常见的问题是哪个更好&#xff1f;无论是工程设计师还是初学者&#xff0c;通常会问这个问题或类似的问题。在本文中介绍了 Abaqus 与 Ansys&#xff0c;您将了解这些问题的答案。 1-ANSYS&…

数据库8之嵌套查询

上一篇文章讲到连接查询&#xff0c;连接查询就是一个一个去查找相匹配的行&#xff0c;再返回给用户看。当我们数据量少的时候我们用连接查询没有太大问题&#xff0c;可是&#xff0c;当数据量大的时候&#xff0c;连接查询效率显然不高。这个时候我们可以用嵌套查询&#xf…

Oracle跨服务器取数——DBlink 初级使用

前言 一句话解释DBlink是干啥用的 实现跨库访问的可能性. 通过DBlink我们可以在A数据库访问到B数据库中的所有信息,例如我们在加工FDS层表时需要访问ODS层的表,这是就需要跨库访问 一、DBlink的分类 private&#xff1a;用户级别&#xff0c;只有创建该dblink的用户才可以使…

Maven 下载及配置详细步骤

1、Maven 下载 Maven 官网地址:https://maven.apache.org/download.cgi(opens new window) 进入 Maven 官网,点击 archives 下载版本 3.6.2 找到下载的压缩包并解压

传统协议大比拼之IP协议

文章目录 一、引言二、IP协议的基本特点2.1 IP协议的作用和基本功能2.2 地址管理手动分配IP动态主机配置协议(DHCP)网络地址转换(NAT)IPv6 2.2 路由选择RIP(距离向量型的协议)OSPF(链路状态类型协议)BGP(边界网关协议) 2.3 IP协议的特点&#xff1a; 三、IP地址的组成3.1 IP地址…