Linux--进程概念

目录

基本概念

描述进程-PCB

 task_struct-PCB的一种

task_struct内容分类 

查看进程

通过系统目录查看

通过ps命令查看

通过系统调用获取进程的PID和PPID

通过系统调用创建进程- fork初始 

Linux进程状态 

运行状态(Running)- R

浅度睡眠状态(Light Sleep)- S

深度睡眠状态(Deep Sleep)- D:

暂停状态(Paused)- T

僵尸状态(Zombie)- Z

死亡状态-X

僵尸进程

僵尸进程的危害

孤儿进程


基本概念

课本概念:一个运行起来(加载到内存)的程序或者在内存中的程序

内核观点:担当分配系统资源(CPU时间,内存)的实体。

只要写过代码的都知道,当你的代码进行编译链接后便会生成一个可执行程序,这个可执行程序本质上是一个文件,是放在磁盘上的。当我们双击这个可执行程序将其运行起来时,本质上是将这个程序加载到内存当中了,因为只有加载到内存后,CPU才能对其进行逐行的语句执行,而一旦将这个程序加载到内存后,我们就不应该将这个程序再叫做程序了,严格意义上将应该将其称之为进程。

描述进程-PCB

系统当中可以同时存在大量进程,使用命令ps aux便可以显示系统当中存在的进程。 

而当你开机的时候启动的第一个程序就是我们的操作系统(即操作系统是第一个加载到内存的),我们都知道操作系统是做管理工作的,而其中就包括了进程管理。而系统内是存在大量进程的,那么操作系统是如何对进程进行管理的呢?
这时我们就应该想到管理的六字真言:先描述,再组织。操作系统管理进程也是一样的,操作系统作为管理者是不需要直接和被管理者(进程)直接进行沟通的,当一个进程出现时,操作系统就立马对其进行描述,之后对该进程的管理实际上就是对其描述信息的管理。
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合,课本上称之为PCB(process control block)。

操作系统将每一个进程都进行描述,形成了一个个的进程控制块(PCB),并将这些PCB以双链表的形式组织起来。

这样一来,操作系统只要拿到这个双链表的头指针,便可以访问到所有的PCB。此后,操作系统对各个进程的管理就变成了对这条双链表的一系列操作。
例如创建一个进程实际上就是先将该进程的代码和数据加载到内存,紧接着操作系统对该进程进行描述形成对应的PCB,并将这个PCB插入到该双链表当中。而退出一个进程实际上就是先将该进程的PCB从该双链表当中删除,然后操作系统再将内存当中属于该进程的代码和数据进行释放或是置为无效。
总的来说,操作系统对进程的管理实际上就变成了对该双链表的增、删、查、改等操作。

 task_struct-PCB的一种

进程控制块(Process Control Block,PCB)是操作系统中用于管理和跟踪进程信息的数据结构。每个正在运行的进程都有一个相关联的 PCB,它包含了操作系统需要了解的关于该进程的所有信息。

进程控制块(PCB)是描述进程的,在C++当中我们称之为面向对象,而在C语言当中我们称之为结构体,既然Linux操作系统是用C语言进行编写的,那么Linux当中的进程控制块必定是用结构体来实现的。

PCB实际上是对进程控制块的统称,在Linux中描述进程的结构体叫做task_struct。
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含进程的信息。

task_struct内容分类 

task_struct 是 Linux 内核中用于表示进程的数据结构,也是 Linux 中的进程控制块(PCB)。它包含了描述进程状态和管理进程所需的各种信息。你提到的 task_struct 中的主要信息如下:

  1. 标识符(Identifier):用来唯一标识该进程的信息,通常由进程 ID(PID)表示,以区别其他进程。

  2. 状态(State):描述了进程的当前状态,如运行、就绪、阻塞等,以及与进程退出相关的信息,如退出代码和退出信号。

  3. 优先级(Priority):相对于其他进程的优先级,影响进程的调度顺序。

  4. 程序计数器(Program Counter):记录了下一条即将被执行的指令的地址。

  5. 内存指针(Memory Pointers):包括了指向程序代码、进程数据以及与其他进程共享的内存块的指针。

  6. 上下文数据(Context Data):保存了进程执行时处理器寄存器中的数据,用于在进程被调度时恢复其执行现场。

  7. I/O 状态信息(I/O State Information):包括了进程发起的 I/O 请求、分配给进程的 I/O 设备以及进程正在使用的文件列表等信息。

  8. 记账信息(Accounting Information):记录了处理器时间总和、使用的时钟总和、时间限制等与进程执行相关的计数信息。

  9. 其他信息:可能还包括了与进程相关的其他信息,如进程的父进程、子进程列表等。

task_struct 在 Linux 内核中扮演着重要的角色,它是操作系统管理和调度进程的关键数据结构之一,通过对其内容的管理和更新,操作系统可以有效地管理系统中运行的所有进程。

查看进程

通过系统目录查看

在根目录下有一个名为proc的系统文件夹。

文件夹当中包含大量进程信息,其中有些子目录的目录名为数字。

这些数字其实是某一进程的PID,对应文件夹当中记录着对应进程的各种信息。

通过ps命令查看

单独使用ps命令,会显示所有进程信息。

ps命令与grep命令搭配使用,即可只显示某一进程的信息。

 

通过系统调用获取进程的PID和PPID

通过使用系统调用函数,getpid和getppid即可分别获取进程的PID和PPID。
我们可以通过一段代码来进行测试。

 

通过系统调用创建进程- fork初始 

fork是一个系统调用级别的函数,其功能就是创建一个子进程。

每出现一个进程,操作系统就会为其创建PCB,fork函数创建的进程也不例外。

 

我们知道加载到内存当中的代码和数据是属于父进程的,那么fork函数创建的子进程的代码和数据又从何而来呢?

当使用 fork() 函数创建子进程时,子进程会复制父进程的地址空间,包括代码段、数据段、堆栈等。这种复制是通过操作系统的页表机制来实现的。具体来说,当调用 fork() 函数时,操作系统会创建一个新的进程控制块(PCB)用于子进程,并分配给子进程一个独立的进程标识符(PID)。然后,操作系统会将父进程的地址空间完整地复制到子进程的地址空间中,包括代码、数据、堆和栈等。这样,子进程就拥有了与父进程相同的代码和数据,但是它们是独立的,各自运行在自己的进程上下文中。

这种复制是通过写时复制(Copy-on-Write,COW)技术来实现的。具体来说,在 fork() 调用之后,父进程和子进程会共享相同的物理内存页。只有当父进程或子进程尝试修改共享的内存页时,操作系统才会进行实际的复制操作,以确保父进程和子进程之间的内存数据不会相互影响。这样,当父进程和子进程都只是读取共享的内存时,它们可以共享相同的物理内存页,提高了内存利用效率。

总之,fork() 函数创建的子进程的代码和数据来自于父进程的地址空间的复制,通过写时复制技术来实现内存共享和延迟复制,从而提高了系统的性能和效率。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    int data = 100;
    pid_t pid;

    // 使用 fork() 函数创建子进程
    pid = fork();

    if (pid == -1) {
        // 创建子进程失败
        perror("fork");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("Child Process: PID=%d, Parent PID=%d\n", getpid(), getppid());
        // 在子进程中修改变量的值
        data = 200;
        printf("Child Process: data=%d\n", data);
    } else {
        // 父进程
        printf("Parent Process: PID=%d, Child PID=%d\n", getpid(), pid);
        // 在父进程中打印变量的值
        printf("Parent Process: data=%d\n", data);
    }

    return 0;
}

运行该程序,你会看到父子进程会打印出不同的进程ID,并且父子进程中的 data 变量的值也会有所不同。这是因为在调用 fork() 函数后,子进程会复制父进程的地址空间,包括变量的值,但之后父子进程的变量是独立的,各自的修改不会影响到对方。

Parent Process: PID=1234, Child PID=1235
Parent Process: data=100
Child Process: PID=1235, Parent PID=1234
Child Process: data=200

这个例子展示了父子进程共享变量的情况,但在实际的使用中,父子进程通常会在不同的工作区域内操作数据,以避免因为同时访问共享数据而导致的竞态条件和数据不一致性问题。 

Linux进程状态 

一个进程从创建而产生至撤销而消亡的整个生命期间,有时占有处理器执行,有时虽可运行但分不到处理器,有时虽有空闲处理器但因等待某个时间的发生而无法执行,这一切都说明进程和程序不相同,进程是活动的且有状态变化的,于是就有了进程状态这一概念。

  

运行状态(Running)- R

  • 概念:进程正在执行并占用 CPU 时间,在操作系统的调度策略下,CPU 分配时间片给该进程执行。
  • 在操作系统中的体现:在操作系统的进程调度队列中,处于运行状态的进程被调度执行,CPU 执行其指令,直到其时间片用完或者主动放弃 CPU。

一个进程处于运行状态(running),并不意味着进程一定处于运行当中,运行状态表明一个进程要么在运行中,要么在运行队列里。也就是说,可以同时存在多个R状态的进程。

小贴士: 所有处于运行状态,即可被调度的进程,都被放到运行队列当中,当操作系统需要切换进程运行时,就直接在运行队列中选取进程运行。

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Running Process: PID=%d\n", getpid());
    sleep(5); // 模拟进程执行
    return 0;
}

该程序简单地打印出当前进程的 PID,并模拟了进程执行的过程,通过 sleep(5) 函数让进程休眠 5 秒。

在终端中打印出当前进程的 PID,然后程序会在运行状态下等待 5 秒,之后退出。

浅度睡眠状态(Light Sleep)- S

  • 概念:进程处于等待某些事件的状态,但仍然可以很快地被唤醒,不会阻塞其他进程。
  • 在操作系统中的体现:进程调用了一些阻塞型的系统调用,如等待 I/O 操作完成或等待信号量,进入睡眠状态,但不会阻塞其他进程的执行。当事件发生时,进程会被唤醒,转为就绪状态。
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Light Sleep Process: PID=%d\n", getpid());
    // 模拟等待键盘输入
    char buffer[100];
    fgets(buffer, sizeof(buffer), stdin);
    return 0;
}

该程序打印出当前进程的 PID,并等待用户在终端输入。使用 fgets() 函数等待用户输入,此时进程处于浅度睡眠状态。

在终端中打印出当前进程的 PID,然后程序会等待用户输入。当用户输入完成并按下回车键后,程序退出。

深度睡眠状态(Deep Sleep)- D

  • 概念:进程处于等待某些事件的状态,且需要较长时间才能被唤醒,期间不占用 CPU 时间。
  • 在操作系统中的体现:进程调用了一些长时间阻塞的系统调用,如等待键盘输入或者等待网络数据到达,进入深度睡眠状态。在等待的过程中,进程不会占用 CPU 时间,直到事件发生并且进程被唤醒,才转为就绪状态。
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Deep Sleep Process: PID=%d\n", getpid());
    // 模拟等待网络数据到达
    sleep(10);
    return 0;
}

该程序打印出当前进程的 PID,并通过 sleep(10) 函数模拟了等待网络数据到达的过程,此时进程处于深度睡眠状态。

在终端中打印出当前进程的 PID,然后程序会休眠 10 秒,之后退出。

暂停状态(Paused)- T

  • 概念:进程暂时停止执行,不占用 CPU 时间,可以通过发送信号或其他方式来唤醒。
  • 在操作系统中的体现:进程被暂停执行,不会占用 CPU 时间,不参与调度。它可能处于等待某个条件满足的状态,例如等待信号量的释放。进程可以通过收到信号或其他事件的方式被唤醒,然后重新进入就绪状态。
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Paused Process: PID=%d\n", getpid());
    // 进入暂停状态
    pause();
    printf("Resumed Process: PID=%d\n", getpid()); // 不会被执行
    return 0;
}

 该程序打印出当前进程的 PID,并调用 pause() 函数让进程进入暂停状态,直到接收到信号才会被唤醒。

在终端中打印出当前进程的 PID,然后程序会一直处于暂停状态,不再继续执行,直到接收到信号为止。

僵尸状态(Zombie)- Z

  • 概念:进程已经结束执行,但其进程描述符仍然存在,直到父进程调用 wait()waitpid() 函数来获取子进程的退出状态信息后才会被清除。
  • 在操作系统中的体现:当一个进程结束执行后,它的进程描述符会保留在系统中,此时进程成为僵尸进程。僵尸进程不再占用 CPU 时间,但占用系统资源。当父进程调用 wait()waitpid() 函数时,操作系统会清除僵尸进程的进程描述符,释放相关资源。
#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("Child Process: PID=%d\n", getpid());
        return 0;
    } else {
        // 父进程
        printf("Parent Process: PID=%d, Child PID=%d\n", getpid(), pid);
        // 等待子进程结束
        sleep(10);
        return 0;
    }
}

 该程序创建一个子进程,子进程打印出自己的 PID 后立即退出,此时子进程的进程描述符会保留在系统中,成为僵尸进程。父进程等待一段时间后退出。

在终端中先打印出父进程和子进程的 PID,然后父进程会等待 10 秒,之后退出。子进程会立即退出,成为僵尸进程。可以通过 ps 命令查看僵尸进程。

死亡状态-X

死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了,所以你不会在任务列表当中看到死亡状态(dead)。

僵尸进程

一个进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态。而处于僵尸状态的进程,我们就称之为僵尸进程。

例如,对于以下代码,fork函数创建的子进程在打印5次信息后会退出,而父进程会一直打印信息。也就是说,子进程退出了,父进程还在运行,但父进程没有读取子进程的退出信息,那么此时子进程就进入了僵尸状态。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
	printf("I am running...\n");
	pid_t id = fork();
	if(id == 0){ //child
		int count = 5;
		while(count){
			printf("I am child...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);
			sleep(1);
			count--;
		}
		printf("child quit...\n");
		exit(1);
	}
	else if(id > 0){ //father
		while(1){
			printf("I am father...PID:%d, PPID:%d\n", getpid(), getppid());
			sleep(1);
		}
	}
	else{ //fork error
	}
	return 0;
} 

当子进程退出后,子进程的状态就变成了僵尸状态。

僵尸进程的危害

1.僵尸进程的退出状态必须一直维持下去,因为它要告诉其父进程相应的退出信息。可是父进程一直不读取,那么子进程也就一直处于僵尸状态。
2.僵尸进程的退出信息被保存在task_struct(PCB)中,僵尸状态一直不退出,那么PCB就一直需要进行维护。
3.若是一个父进程创建了很多子进程,但都不进行回收,那么就会造成资源浪费,因为数据结构对象本身就要占用内存。
4.僵尸进程申请的资源无法进行回收,那么僵尸进程越多,实际可用的资源就越少,也就是说,僵尸进程会导致内存泄漏。

孤儿进程

在Linux当中的进程关系大多数是父子关系,若子进程先退出而父进程没有对子进程的退出信息进行读取,那么我们称该进程为僵尸进程。但若是父进程先退出,那么将来子进程进入僵尸状态时就没有父进程对其进行处理,此时该子进程就称之为孤儿进程。
若是一直不处理孤儿进程的退出信息,那么孤儿进程就会一直占用资源,此时就会造成内存泄漏。因此,当出现孤儿进程的时候,孤儿进程会被1号init进程领养,此后当孤儿进程进入僵尸状态时就由int进程进行处理回收。

例如,对于以下代码,fork函数创建的子进程会一直打印信息,而父进程在打印5次信息后会退出,此时该子进程就变成了孤儿进程。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
	printf("I am running...\n");
	pid_t id = fork();
	if(id == 0){ //child
		int count = 5;
		while(1){
			printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid(), count);
			sleep(1);
		}
	}
	else if(id > 0){ //father
		int count = 5;
		while(count){
			printf("I am father...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);
			sleep(1);
			count--;
		}
		printf("father quit...\n");
		exit(0);
	}
	else{ //fork error
	}
	return 0;
} 

在父进程未退出时,子进程的PPID就是父进程的PID,而当父进程退出后,子进程的PPID就变成了1,即子进程被1号进程领养了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

工业级3D开发引擎HOOPS:创新与效率的融合!

在当今这个技术日新月异的时代&#xff0c;3D技术已成为推动各行各业发展的重要力量。从工程设计到游戏开发&#xff0c;从虚拟现实到增强现实&#xff0c;3D技术的应用无处不在&#xff0c;它极大地丰富了我们的生活和工作。而在这样的背景下&#xff0c;HOOPS作为一个强大的3…

java学习和项目总结

java&#xff1a; JDK/JRE/JVM三者的关系 JVM&#xff1a;JVM是java进行编译的虚拟机&#xff0c;是Java 能够跨平台运行的核心 。 所有的java程序会首先被编译为.class的类文件&#xff0c;这种类文件可以在虚拟机上执行。也就是说class文件并不直接与机器的操作系统交互&a…

认识NXP新型微处理器:MCX工业和物联网微控制器

目录 概述 1 MCX工业和物联网微控制器介绍 2 MCX 系列微控制器类型 2.1 MCX N系列微控制器 2.1.1 主要特征 2.1.2 MCX N系列产品 2.1.3 MCX N9xx和N5xx MCU选型表 2.2 MCX A系列微控制器 2.2.1 主要特征 2.2.2 MCX A系列产品 2.2.3 MCX A MCU的架构 2.3 MCX W系…

144.栈和队列:有效的括号(力扣)

题目描述 代码解决 class Solution { public:bool isValid(string s) {// 如果字符串长度为奇数&#xff0c;不可能是有效的括号字符串if(s.size() % 2 ! 0) return false;// 使用栈来存放括号stack<char> st;// 遍历字符串中的每一个字符for(int i 0; i < s.size();…

C# run Node.js

C# run nodejs Inter-Process Communication&#xff0c;IPC Process类 启动Node.js进程&#xff0c;通过标准输入输出与其进行通信。 // n.js// 监听来自标准输入的消息 process.stdin.on(data, function (data) {// 收到消息后&#xff0c;在控制台输出并回复消息console.l…

2024最新 Jenkins + Docker 实战教程(三) - 在Jenkins服务器上运行java项目

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Kafka SASL_SSL集群认证

背景 公司需要对kafka环境进行安全验证,目前考虑到的方案有Kerberos和SSL和SASL_SSL,最终考虑到安全和功能的丰富度,我们最终选择了SASL_SSL方案。处于知识积累的角度,记录一下kafka SASL_SSL安装部署的步骤。 机器规划 目前测试环境公搭建了三台kafka主机服务,现在将详…

【js刷题:数据结构链表之环形链表】

环形链表 一、题目二、实现思路1.判断是否有环2.如何找到环状链表的入口节点 三、解题代码 一、题目 二、实现思路 主要有两点&#xff0c;首先我们要判断这个链表是否有环&#xff0c;其次我们要找到这个环的入口节点。 1.判断是否有环 定义一个快指针fast和慢指针slow 快指…

基于.NetCore和ABP.VNext的项目实战二:Swagger

Mag.Blog.Swagger层添加Volo.Abp.AspNetCore和Swashbuckle.AspNetCore包,引用实体层.Domain 添加模块类MagBlogSwaggerModule.cs,依赖MagBlogDomainModule模块,并且重写ConfigureServices和OnApplicationInitialization方法 namespace Mag.Blog.Swagger {[DependsOn(typeof…

【机器学习】在电子商务(淘*拼*京*—>抖)的应用分析

机器学习与大模型&#xff1a;电子商务的新引擎 一、电子商务的变革与挑战二、机器学习与大模型的崛起三、机器学习与大模型在电子商务中的应用实践个性化推荐精准营销智能客服库存管理与商品定价 四、总结与展望 随着互联网的飞速发展&#xff0c;电子商务已经成为我们生活中不…

c++ 实现 梯度下降线性回归模型

理论与python实现部分 3.1. 线性回归 — 动手学深度学习 2.0.0 documentation c代码 没能力实现反向传播求梯度&#xff0c;只能自己手动算导数了 #include <bits/stdc.h> #include <time.h> using namespace std;//y_hat X * W b // linreg 函数&#xff1a…

Unity射击游戏开发教程:(24)创造不同的敌人

在这篇文章中,我们将讨论添加一个可以承受多次攻击的新敌人和一些动画来使事情变得栩栩如生。敌人没有任何移动或射击行为。这将有助于增强未来敌人的力量。 我们将声明一个 int 来存储敌人可以承受的攻击数量,并将其设置为 3。

深度学习之基于Matlab卷积神经网络(CNN)手写数字识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 手写数字识别是计算机视觉领域的一个重要问题&#xff0c;也是深度学习应用的一个典型场景。卷…

基于消息中间件的异步通信机制在系统解耦中的优化与实现

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起进步&am…

Docker Compose快速入门

本教程旨在通过指导您开发基本Python web应用程序来介绍Docker Compose的基本概念。 使用Flask框架&#xff0c;该应用程序在Redis中提供了一个命中计数器&#xff0c;提供了如何在web开发场景中应用Docker Compose的实际示例。 即使您不熟悉Python&#xff0c;这里演示的概念也…

Llama模型家族之使用 Supervised Fine-Tuning(SFT)微调预训练Llama 3 语言模型(一) LLaMA-Factory简介

LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;三&#xff09; 基于 LlaMA…

以色列人Andi Gutmans开发的php zend

虽然目前php语言不行了【相关的文章前几年已经有人发过】&#xff0c;但这不是重点&#xff0c;重点是zend引擎的东西具有极大的技术价值&#xff0c;负责zend引擎实现的大佬都现在差不多都是40&#xff0c;50岁左右了&#xff0c;从1997&#xff0c;1998&#xff0c;2000到202…

Java基础之进制转换和位运算专题

什么是进制&#xff1f; 是数学中的一个概念&#xff0c;就是数据“逢几进位”。 例如&#xff1a;生活中用的计数方法 ---- 十进制。十进制就是数字逢十就要进一位。 例如&#xff1a;一个星期有7天&#xff0c;就是逢七进一&#xff1b;一个月有30天就是逢30进一&#xff1b;…

基于单片机和蓝牙控制的智能小车设计

摘要 &#xff1a; 本文设计了一种以智能手机为平台控制小车的控制系统&#xff0c;该系统以蓝牙为通信模块&#xff0c;手机通过蓝牙发送信号给小 车上的蓝牙模块&#xff0c;从而驱动电机实现小车各种运动&#xff0c;提供了一种无线遥控小车的新思路。设计了该系统的硬件与软…

思维导图-VPN

浏览器集成了受信任的机构的证书