1.进程概述

进程和程序的区别

程序:程序是存储在存储介质上的一个可执行文件---静态的

进程:进程是程序的执行实例。可以说进程就是正在执行的程序。

程序是一些指令的集合,而进程是程序的执行过程,这个过程的状态是变化的,包括进程的创建、调度和消亡。

单道程序和多道程序

单道程序:所有进程一个一个排队执行。若 A 阻塞,B 只能等待,即使 CPU 处于空闲状态。而在人机交互时阻塞的出现是必然的。所有这种模型在系统资源利用上及其不合理,在计算机发展历史上存在不久,大部分便被淘汰了。以前的dos操作系统就是单道程序系统

多道程序:在计算机内存中同时存放几道相互独立的程序,它们在管理程序控制之下,相互穿插的运行(并行和并发)。多道程序设计必须有硬件基础作为保证。

并行和并发

并行:同一时间内,多个程序或多条指令同时运行(一定是多核)

并发:宏观上的并行,多个进程使用同一块资源(CPU),但是他们快速的交叉使用CPU资源,给人一种假象,像是多个程序同时执行。

进程控制块(PCB-process contrl block)

进程在内存中运行时,内核为每个进程分配一个 PCB(进程控制块),维护进程相关的信息(包括数据、代码存放点位置灯),Linux 内核的进程控制块是 task_struct 结构体。相当于人的身份证。

PCB是操作系统中最重要的记录型数据结构。

2.进程的状态

三种状态

就绪态:进程已经具备执行的一切条件,正在等待CPU的处理时间

执行态:该进程正在占用CPU运行

等待态:进程因不具备某些执行条件而暂时无法继续执行的状态

ps指令:用于显示当前进程的状态,类似于windows的任务管理器

参数:

ps 的参数非常多, 在此仅列出几个常用的参数并大略介绍含义

-A 列出所有的进程

-w 显示加宽可以显示较多的资讯

-au 显示较详细的资讯

-aux 显示所有包含其他使用者的进程

-ajx 更详细、更具层次关系

USER:进程拥有者
PID:process id
%CPU:占用CPU使用率
%NAME:占用的记忆体使用率
VSZ:占用的虚拟记忆体大小
RSS:占用的记忆体大小
TTY:终端的次要装置号码
STAT:该进程的状态
START:进程开始时间
TIME:执行的时间
COMMAND:执行的命令

stat状态:

3.进程号

每个进程都有一个进程号来进标识,其类型是pid_t,进程号的范围是0~32767(不同的版本可能有区别)。进程号总是唯一的,但是可以重用,一个进程终止后,这个进程号可以再次被使用。

  • linux中的进程号从0开始
    • 进程号0和1由内核创建
      • 0进程:调度进程,常被称为交换进程
      • 1进程:init进程
    • 除调度进程外,所有进程都由1进程进行直接或者间接调用

PID:进程号,是一个非负整数

PPID:父进程,任何进程都由另一个进程创建,高金城称为被创建进程的父进程。对应的进程号称为父进程号(PPID);

PGID:进程组,一个或多个进程的集合。他们之间相互关联,进程组可以接受同一终端的各种信号,关联的进程有一个进程组号。

获取进程函数

#include <sys/types.h>
#include <unistd.h>//功能:获取本进程号
//返回值:当前进程进程号
pid_t getpid(void);//功能:获取调度此函数的进程的父进程号
pid_t getppid(void);//功能:获取进程组好,参数为0是返回当前PGID,否则返回指定的进程的PGID
pid_t getpgid(pid_t pid);

        

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char const *argv[])
{printf("该进程号:%d\n",getpid());printf("该进程的父进程号:%d\n",getppid());printf("该进程的进程组号:%d\n",getpgid(0));return 0;
}

4.创建进程

fork函数(创建的子进程是复制一份父进程的内容,到一个新的空间)

#include <sys/types.h>
#include <unistd.h>
//创建一个新进程
pid_t fork(void)
功能:
/*fork()函数用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进
程称为父进程。
返回值:
成功:子进程中返回 0,父进程中返回子进程 ID。 非常重要!!!!!
失败:返回-1*/

注意:子进程是将父进程里面的内容全部拷贝了一份,但是子进程里的fork不会再执行,会从fork的下一句开始执行,即将得到的id赋值给pid,所以fork函数在父进程里返回值为子进程id,在子进程里返回值为0(系统自动处理,避免一直执行fork,子子孙孙无穷尽也)

因为父子进程同时再运行,所以才会有两个结果(否则 if与elseif是互斥的,不可能有两个结果)

#include <sys/types.h>#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>int main(int argc, char **argv)
{pid_t pd = fork();if (pd < 0){perror("fork");return 1;}else if (pd == 0){while (1){printf("子进程ID-->%d,父进程ID-->%d\n", getpid(), getppid());sleep(5);}}else if (pd > 0){while (1){printf("父进程ID-->%d\n", getpid());sleep(2);}}return 0;
}

sleep函数

是将进程暂时挂起一段时间,在后续资源回收详细介绍,可以在此处理解为延时一段时间

#include<unistd.h>
unsigned int sleep(unsigned int sec);  //进程挂起一段时间,即一直处在等待态
功能:
进程挂起指定的描述,知道指定的时间用完或者收到信号才解除挂起
返回值:
若进程挂起到sec指定的时间,则返回0,若有信号中断,则返回剩余的秒数
注意:
进程挂起指定的秒数后程序不会立即执行,系统知识将此进程切换到就绪态

父子进程的复制

使用 fork 函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间。

地址空间:包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。子进程所独有的只有它的进程号,计时器等。因此,使用 fork 函数的代价是很大的

进程之间都是相互独立的。

vfork函数(子进程和父进程共享一块内存空间)

#include <sys/types.h>
#include <unistd.h>//pid_t vfork(void);创建一个新进程,但是vfork在创建进程的时候,先创建子进程,在创建父进程
/*功能:vfork()函数用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程,子进程需要退出之后(exit()),父进程才可以执行。
返回值:成功:子进程中返回 0,父进程中返回子进程 ID。失败:返回-1。*/
pid_t vfork(void);

先创建子进程,再创建父进程

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char const *argv[])
{pid_t pd = vfork();if (pd<0){perror("vfork");}else if (pd > 0){while (1){sleep(1);printf("当前进程id:%d\n",getpid());_exit(1);  //退出,结束当前进程}}else if (pd == 0){while (1){sleep(1);printf("当前进程id:%d 当前进程父进程:%d\n",getpid(),getppid());_exit(1); //退出,结束当前进程} }return 0;
}

子进程父进程共享同样的内存

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char const *argv[])
{int num = 0;pid_t pd = vfork();if (pd < 0){perror("vfork");}else if (pd == 0){while (1){sleep(1);printf("当前进程id:%d 当前进程父进程:%d\n", getpid(), getppid());num = 1000;printf("num:%d\n", num);_exit(1); // 退出,结束当前进程}}else if (pd > 0){while (1){sleep(1);printf("当前进程id:%d\n", getpid());printf("num:%d\n", num);_exit(1); // 退出,结束当前进程}}return 0;
}

5.进程资源回收(等待)

当进程结束后,系统可以回收进程资源,但是关于进程中的例如ID号,内存地址,进程名等,系统不会回收。父子进程有序需要简单的进程间同步,比如父进程等待子进程结束。

一般指的是父进程回收子进程。

wait函数:

#include <sys/types.h>
#include <sys/wait.h>/*
功能:等待子进程终止,如果子进程终止了,那么此函数会回收子进程的资源。调用wait函数的进程会挂起(阻塞),因为要等待子进程结束。如果有多个子进程,需要调用多个wait函数
参数:status,子进程退出时的状态,需要提前定义,后续会把状态存储到这里
返回值:
成功:子进程的进程号
失败:-1
*/
pid_t wait(int *wstatus);

案例:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{pid_t pd = fork();if (pd < 0){perror("fork");}else if (pd == 0){int i = 0;for ( i = 5; i > 0; i--){printf("子进程%u剩余生命:%d\n",getpid(),i);sleep(1);}exit(-1);}else if (pd > 0){int status = 0 ;printf("父进程%u正在等待子进程%u结束\n",getpid(),pd);wait(&status);printf("子进程%u结束.\n",pd);printf("退出状态为%d\n",status);}return 0;
}

exit函数

#include <stdlib.h>
void exit(int status);  //退出进程,这是库函数写法,等价于_exit(int status)

wait参数status详解

1.取出子进程的退出信息 WIFEXITED(status) 如果子进程是正常终止的,取出的字
段值非零

2. WEXITSTATUS(status) 返回子进程的退出状态,退出状态保存在status 变量的 8~16 位。在用此宏前应先用宏 WIFEXITED 判断子进程是否正常退出,正常退出才可以使用此宏。

3.此 status 是个 wait 的参数指向的整型变量

    else if (pd > 0){int status = 0;printf("父进程%u正在等待子进程%u结束\n", getpid(), pd);pid_t son = wait(&status); //保存退出的那个pidif (WIFEXITED(status))  //如果正常退出{printf("子进程%u结束.\n", son);printf("退出状态为%d\n", WEXITSTATUS(status)); //打印退出状态}}

waitpid函数

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

函数文档注释:

功能:等待子进程终止,如果子进程终止了,此函数会回收子进程的资源。

参数:

        pid : 参数 pid 的值有以下几种类型:
pid > 0 等待进程 ID 等于 pid 的子进程。
pid = 0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程
组,waitpid 不会等待它。
pid = -1 等待任一子进程,此时 waitpid 和 wait 作用一样。
pid < -1 等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。例如传入-5555,对应的作用是监控进程组号为5555的每一个pid
status : 进程退出时的状态信息。和 wait() 用法一样。
options : options 提供了一些额外的选项来控制 waitpid()。
0:同 wait(),阻塞父进程,等待子进程退出。
WNOHANG:没有任何已经结束的子进程,则立即返回(非阻塞)。
WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程
的结束状态。(由于涉及到一些跟踪调试方面的知识,加之极少用到)

返回值: waitpid() 的返回值比 wait() 稍微复杂一些,一共有 3 种情况:
1) 当正常返回的时候,waitpid() 返回收集到的已经回收子进程的进程号;
2) 如果设置了选项 WNOHANG,而调用中 waitpid() 的进程发现有子进程在运行,而且没有已退出的子进程,则返回0;如果父进程所有子进程都结束,则返回-1;如果>0,则等到了一个子进程退出,这个返回值就是退出的那个子进程
可等待
3) 如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误
所在。

代码案例:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{pid_t pd = fork();if (pd < 0){perror("fork");}else if (pd == 0){int i = 0;for (i = 5; i > 0; i--){printf("子进程%u剩余生命:%d\n", getpid(), i);sleep(1);}exit(0);}else if (pd > 0){int status = 0;printf("父进程%u正在等待子进程%u结束\n", getpid(), pd);sleep(6);pid_t son = waitpid(-1,&status,WNOHANG);  //6s后检测是否有子进程退出,如果有,则返回对应进程idprintf("son %u\n",son);if (WIFEXITED(status)){printf("子进程%u结束.\n", son);printf("退出状态为%d\n", WEXITSTATUS(status));}}return 0;
}

6.特殊进程

6.1僵尸进程

进程已经结束,但是对应的资源没有回收,这样的进程称之为僵尸进程。例如:子进程已退出,但是父进程未回收子进程资源(wait,waitpid),那么此子进程就成为僵尸进程。

有危害:因为子进程id被占用,但是系统的pid数量有限制

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{pid_t pd = fork();if (pd < 0){perror("fork");}else if (pd == 0){int i = 0;for (i = 5; i > 0; i--){printf("子进程%u剩余生命:%d\n", getpid(), i);sleep(1);}exit(0);}else if (pd > 0){//子进程结束,父进程不回收资源while (1);//父进程不能结束,否则他们会一起被回收}return 0;
}

6.2孤儿进程

父进程先结束,但是子进程未运行结束的子进程。

子进程被1号进程接管,当子进程结束时,由1号进程进行资源回收

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{pid_t pd = fork();if (pd < 0){perror("fork");}else if (pd == 0){while (1);int i = 0;for (i = 5; i > 0; i--){printf("子进程%u剩余生命:%d\n", getpid(), i);sleep(1);}exit(0);}else if (pd > 0){printf("父进程%u结束\n",getpid());}return 0;
}

6.3守护进程(精灵进程)

脱离终端的孤儿进程

暂时不写,后续再写。继续往后学下去吧

7.fork创建多个子进程

创建2个子进程

1.错误的写法

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{for (int i = 0; i < 2; i++){pid_t pid = fork();}while(1);return 0 ;
}

有4个a.out相关进程

实际是1个父进程,2个子进程,还有1个子进程的子进程,一共4个。

这样会有问题,不知道哪个进程是子进程

2.正确的进程

起始上面的代码只要能保证子进程不会再创建子进程即可

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{for (int i = 0; i < 2; i++){pid_t pid = fork();if (pid == 0) //如果是在子进程里,就不再往下运行{break;}}while(1);return 0 ;
}

3.创建多进程并且回收资源

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{int i = 0;for (i = 0; i < 2; i++){pid_t pid = fork();if (pid == 0){break;}}if (i == 0){printf("第1个子进程id:%u\n",getpid());sleep(3);exit(1);}else if(i == 1){printf("第2个子进程id:%u\n",getpid());sleep(4);exit(1);}else if(i == 2){int ret = 0;printf("父进程Id:%u\n",getpid());while(1){ret = waitpid(-1,NULL,WNOHANG);if (ret == 0){continue;}else if (ret > 0){printf("进程%u退出\n",ret);}else if(ret < 0){break;}}}return 0 ;
}

8.终端

在 UNIX 系统中,用户通过终端登录系统后得到一个 Shell 进程,这个终端成为Shell 进程的控制终端(Controlling Terminal),进程中,控制终端是保存在 PCB中的信息,而 fork 会复制 PCB 中的信息,因此由 Shell 进程启动的其它进程的控制终端也是这个终端。

举个例子:

int num = 0;
scanf("%d",&num);
printf("%d\n",num);
while(1);

在终端中,得到一个shell进程,这个shell进程的PCB中保存着终端控制信息,因此他可以控制终端。

当检测到a.out的时候,会fork一个子进程来完成a.out的执行,fork的子进程里面不会再有./a.out这个执行执行

子进程中没有./a.out,也会执行,这是由exec族函数完成的,后续会讲。此时shell会把中断控制权交给a.out,所以在a.out执行的时候,输入任何命令都不起作用。a.out结束后,终端控制权移交给shell进程

此时,如果a.out也创建一个子进程呢?

代码案例:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{pid_t pd = fork();if (pd == 0){int num = 0;sleep(1);scanf("%d",&num);printf("子进程%u中num=%d,所属终端名为%s\n",getpid(),num,ttyname(0));}else if (pd > 0){int num = 0;sleep(3);scanf("%d",&num);printf("父进程%u中num=%d,所属终端名为%s\n",getpid(),num,ttyname(0));}return 0 ;
}

ttyname函数

/*获取终端名称
返回值:
成功:终端名
失败:NULL
参数:文件描述符*/
char *ttyname(int fd);

进程组

进程组是包含一个或多个进程的集合,属于一个回话,fork不会改变进程组。

进程组ID

每个进程组都有唯一的进程组ID(整数),进程组由进程组ID来唯一标识,除了PID,进程组ID也是一个进程的必备属性之一。

进程组ID一般是当前进程组中的第一个进程的ID(组长)

getpgid();//获得进程组IDint setpgid(pid_t pid,pid_t pgid);//将pid的进程组id设置为pgid。创建一个新进程组或者假如一个已经存在的pgid

会话

多个进程组的集合叫做会话。

没打开一个终端,必打开一个会话。

如果进程ID=进程组ID=会话ID,那么该进程为会话首进程(会长)

会话是一个或多个进程组的集合。 一个会话可以有一个控制终端。这通常是终端设备或伪终端设备; 建立与控制终端连接的会话首进程被称为控制进程; 一个会话中的几个进程组可分为一个前台进程组以及一个或多个后台进程组; 如果一个会话有一个控制终端,则它有一个前台进程组,其它进程组为后台进程组; 如果终端接口检测到断开连接,则将挂断信号发送至控制进程(会话首进程)。

前台进程组:该进程组中的进程能够向终端设备进行读、写操作的进程组。

后台进程组:只能向终端写的进程组。

创建会话

创建会话的函数:

#include <sys/types.h>
#include <unistd.h>
/*
函数功能:创建一个会话,并以自己的 ID 设置进程组 ID,同时也是新会话的 ID。
返回值:
成功:返回调用进程的会话 ID
失败:-1
*/
pid_t setsid(void);

注意事项

1) 调用进程不能是进程组组长,该进程变成新会话首进程(session header)

2) 该调用进程是组长进程,则出错返回

3) 该进程成为一个新进程组的组长进程

4) 需有 root 权限(ubuntu 不需要)

5) 新会话丢弃原有的控制终端,该会话没有控制终端

6) 建立新会话时,先调用 fork, 父进程终止,子进程调用 setsid

代码:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{pid_t pd = fork();if (pd == 0){printf("%u\n",getsid(0));setsid();while(1){}}else if (pd > 0){exit(1);}return 0 ;
}

9.exec函数族

在 Windows 平台下,我们可以通过双击运行可执行程序,让这个可执行程序成为一个进程;而在 Linux 平台,我们可以通过 ./ 运行,让一个可执行程序成为一个进程。 但是,如果我们本来就运行着一个程序(进程),我们如何在这个进程内部启动一个外部程序,由内核将这个外部程序读入内存,使其执行起来成为一个进程呢?这里我们通过 exec 函数族实现。(即可以实现从一个运行的终端启动另一个程序)。

exec 指的是一组函数,一共有 6个

核心特点:

核心特点总结(四大特点)

    1. “换魂不换壳”

      • 不换:进程ID(PID)、父进程、打开的文件描述符、信号设置、环境变量等全部保留原样

      • 全换:进程的代码段、数据段、堆栈等被彻底替换为指定的新程序。

    2. 一山不容二虎

      • exec 调用成功后没有返回值,因为原来的程序代码已经被完全覆盖了,执行逻辑永远跳不到 exec 之后的代码。

      • 如果调用失败(如找不到指定程序),则会返回 -1,并设置 errno,然后继续执行原程序的后续代码。

    3. 一族六将,功能各异

      这是一个函数,包含多个函数(如 execlexecvexecleexecveexeclpexecvp),它们最终都调用同一个系统调用 execve。
#include <unistd.h>
int execl(const char *path, const char *arg, .../* (char *) NULL */);
int execlp(const char *file,cconst char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char
* const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *filename, char *const argv[], char *const envp[]);

一个进程调用 exec 后,除了进程 ID,进程还保留了下列特征不变: 父进程号 进程组号 控制终端 根目录 当前工作目录 进程信号屏蔽集 未处理信号 ...

六个exec函数只有execve是真正的系统调用函数,其他的都是在此基础上封装的库函数。

代码案例1:execl执行ls指令

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{execl("/bin/ls","-a","-l",NULL);printf("hello world\n");return 0 ;
}

exclp执行ls命令(p:即path,env下面的path都包含)

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{//execl("/bin/ls","-a","-l",NULL);execlp("ls","-a","-l",NULL);printf("hello world\n");return 0 ;
}

execv实现:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{//execl("/bin/ls","-a","-l",NULL);char *temp[]={"-a","-l",NULL};execv("/bin/ls",temp);printf("hello world\n");return 0 ;
}

execve实现

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{//execl("/bin/ls","-a","-l",NULL);extern char **environ;char *temp[]={"-a","-l",NULL};execve("/bin/ls",temp,environ);printf("hello world\n");return 0 ;
}

system函数

#include <stdlib.h>
/*system函数会调用fork函数,产生子进程,子进程会调用exec启动/bin/sh -c string来执行参数string所代表的命令,命令执行完毕后返回原调用进程
参数:要执行的字符串
返回值:
如果command为null,则返回非0,一般为1
如果system()在调用/bin/sh时候失败,返回127,其他原因返回-1
注意:system调用成功才会返回对应的返回值,如果连fork都调用失败,肯定不会成功,一般返回0为成功
*/
int system(const char *command);
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{int status = system("ls -al");if (WIFEXITED(status)){printf("the exit status is %d\n",status);}else{printf("非正常退出\n");}return 0 ;
}

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【90页PPT】新能源汽车数字化转型SAP解决方案(附下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2501_92808859/91777751 资料解读&#xff1a;《新能源汽车数字化转型SAP解决方案》 ​​详细资料请看本解读文章的最后内容​​ 在全球汽车产业加速向电…

LSM6DS3姿态芯片和LIS2MD磁力计芯片数据读取(stm32)

上代码main.c 代码示例#include "lsm6ds3.h" #include "lis2md.h"int16_t Acc[3] {0}, GYO[3] {0},Mag[3] {0};int main(void) {I2C_GPIO_Config(); //配置IIC使用端口Delayms(500); //延时Lsm6ds3_Init( ); …

数据传输安全-IKE工作过程

在前面的课程中&#xff0c;你已经掌握了&#xff1a;IPSec的目标&#xff1a;提供机密性、完整性、身份认证、防重放。IPSec的执行者&#xff1a;AH和ESP协议。IPSec的规则手册&#xff1a;SA&#xff08;安全关联&#xff09;&#xff0c;包含了所有保护参数&#xff08;算法…

翻译-同位协同克里金算法

同点协同克里金法 副标题: 地统计学课程 主要内容: 同点协同克里金法 摘要 同点协同克里金法是一种多元地统计学技术,用于借助次要变量(协变量)来估算主要变量。本课程介绍了同点协同克里金法,并将其与简单克里金法和简单协同克里金法进行了比较。通过一个假设的案例…

国家统计局数据分析01——机器学习

我们前面爬取过国家统计局数据爬取——机器学习-CSDN博客的数据,我们接下来就对爬取的数据进行分析。 这是一个完整的数据分析代码,主要用于加载、清洗和探索政府统计数据。让我为你详细解释每个部分: 1. 导入必要的库 import pandas as pd # 数据处理和分析的核心库 imp…

【杂谈】-混沌理论能否赋予机器差异化思考能力?

混沌理论能否赋予机器差异化思考能力&#xff1f; 文章目录混沌理论能否赋予机器差异化思考能力&#xff1f;1、AI与结构化思维的本质特征2、人类思维的独特优势3、混沌算法的创新实践4、混沌算法的作用机理5、混沌算法的应用实例5.1 音乐创作革新5.2 图像生成突破5.3 科学发现…

jQuery.print插件:网页内容打印与导出指南

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;在网页开发中&#xff0c;为了实现内容的打印或导出&#xff0c;可以利用jQuery.print插件&#xff0c;该插件提供了一个简易的解决方案&#xff0c;尤其适用于表格数据。本指南详细介绍了如何使用jQuery.print…

w嵌入式分享合集125

自己的原文哦~ https://blog.51cto.com/whaosoft/14165530 一、常用电路基础公式 1.欧姆定律计算 计算电阻电路中电流、电压、电阻和功率之间的关系。 欧姆定律解释了电压、电流和电阻之间的关系&#xff0c;即通过导体两点间的电流与这两点间的电势差成正比。说…

Docker 核心技术:Union File System

大家好&#xff0c;我是费益洲。UnionFS 作为 Docker 的技术核心之一&#xff0c;实现了 Docker 镜像的分层轻量化构建、容器资源的隔离复用等目的。本文将从核心原理、主流技术实现简单介绍 UnionFS。 核心原理 Linux 的联合文件系统&#xff08;Union File System&#xff0c…

MongoDB 文档模型设计:JSON 结构的灵活性与陷阱

MongoDB 文档模型设计&#xff1a;JSON 结构的灵活性与陷阱第一章&#xff1a;MongoDB 文档模型基础与核心特性1.1 MongoDB 文档模型的哲学基础1.2 文档模型的优势分析1.3 与关系型数据库的深度对比第二章&#xff1a;文档设计模式与最佳实践2.1 嵌入式模式&#xff08;Embeddi…

基于SQL大型数据库的智能问答系统优化

一、食用指南 基于SQL数据库的智能问答系统设计与实现介绍了在数据库中创建表格数据问答系统的基本方法&#xff0c;我们可以向该系统提出关于数据库数据的问题&#xff0c;最终获得自然语言答案。 为了针对数据库编写有效的查询&#xff0c;我们需要向模型提供表名、表结构和…

【计算机网络】前端基础知识Cookie、localStorage、sessionStorage 以及 Token

一、先搞懂&#xff1a;为什么需要这些“存储工具”&#xff1f; 网页是“无状态”的——比如你登录一个网站&#xff0c;浏览器和服务器每次通信都是独立的&#xff0c;服务器默认记不住你是谁。为了让服务器“记住”用户状态&#xff08;比如登录状态、购物车内容&#xff09…

【语法】【C+V】【mermaid】本身常用图表类型用法快查【CSDN不支持,VSCODE可用】

文章目录 Mermaid 简介Mermaid 由三部分组成Mermaid 的使用方法复杂图表结构的技巧饼图简介饼图语法饼图示例雷达图简介雷达图语法雷达图语法细节标题轴曲线选项 雷达图示例时间线简介时间线语法时间线示例树形图简介树形图语法树形图示例数据包图简介数据包图语法1&#xff1a…

不止效率工具:AI 在文化创作中如何重构 “灵感逻辑”?

一、引言1.1 AI 创作的崛起在当今时代&#xff0c;AI 技术在文化创作领域的迅猛发展已成为不可忽视的现象。从文字创作领域中&#xff0c;AI 能够快速生成小说、诗歌&#xff0c;一些新闻媒体也开始运用 AI 撰写新闻稿件&#xff1b;到视觉艺术方面&#xff0c;AI 绘画软件能依…

软考-系统架构设计师 专家系统(ES)详细讲解

个人博客&#xff1a;blogs.wurp.top 一、ES的核心概念与价值 1. 什么是专家系统&#xff08;ES&#xff09;&#xff1f; 专家系统是一种模拟人类专家解决特定领域问题的智能计算机程序系统。它运用特定领域内大量专家水平的知识和经验&#xff0c;进行推理和判断&#xff…

Vue3+TS+Element-Plus+el-tree创建树节点

1、一级树应用效果&#xff1a;代码&#xff1a;MaterialCategory.vue<script setup lang"ts" name"MaterialCategory"> ...... // 创建树&#xff08;一级树&#xff09; const createTree (dataList: IMaterialCategory[]) > {// 将原始数据转…

C++基础(④链表反转(链表 + 迭代 / 递归))

链表反转&#xff08;链表 迭代 / 递归&#xff09; 题目描述&#xff1a;给你单链表的头节点 head&#xff0c;请你反转链表&#xff0c;并返回反转后的链表头节点。 示例&#xff1a;输入链表 1→2→3→4→5 → 输出 5→4→3→2→1。 思路提示&#xff1a;迭代法&#xff1a…

面向企业级产品开发的自动化脚本实战

引言&#xff1a; 在产品开发团队中&#xff0c;设计师、产品经理和工程师之间的协作常常伴随着大量重复性工作&#xff1a;手动整理设计稿链接、更新产品需求文档、同步项目状态...这些工作不仅耗时&#xff0c;还容易出错。本文将带你编写一个Python脚本&#xff0c;自动化这…

科技赋能生态,智慧守护农林,汇岭生态开启农林产业现代化新篇章

在我国&#xff0c;农林业作为国民经济的基础产业&#xff0c;不仅关乎国家粮食安全与生态平衡&#xff0c;更是乡村振兴战略实施的核心领域。近年来&#xff0c;国家高度重视“三农”问题&#xff0c;大力推进乡村振兴战略&#xff0c;强调要实现农业农村现代化&#xff0c;促…

贪心算法面试常见问题分类解析

一、贪心算法问题 1. 跳跃游戏系列 能否到达终点: def canJump(nums):max_reach = 0for i in range(len(nums)):if i > max_reach:return Falsemax_reach = max(max_reach, i + nums[i])return True 最少步数: def jump(nums):jumps = end = max_pos = 0for i in range(l…