Linux中的特殊进程:idle进程(0号进程)、init进程(1号进程,被systemd 取代 )、kthreadd进程(2号进程)

news2025/1/29 14:07:03

文章目录

  • 1. Linux中的三个特殊进程:
  • 2. idle进程、init进程、kthreadd进程的创建
  • 3 kthreadd进程
    • 3.1 kthreadd进程的启动
    • 3.2 kthreadd进程创建子进程
    • 3.3 kthreadd的工作流程总结
  • 4. init进程
    • 4.1 init进程的启动
  • 4. 总结
  • 参考

1. Linux中的三个特殊进程:

idle进程,或者也称为swapper进程。:

  • 该进程是Linux中的第一个进程(线程),PID为0;

  • idle进程是init进程和kthreadd进程(内核线程)的父进程;

init进程:

  • init进程是Linux中第一个用户空间的进程,PID为1;

  • init进程是其他用户空间进程的直接或间接父进程;

需要注意的是,在新版本的centos系统中,已经使用 systemd 取代 init 作为1号进程

kthreadd(内核线程):

  • kthreadd线程是内核空间其他内核线程的直接或间接父进程,PID为2;

  • kthreadd线程负责内核线程的创建工作;

2. idle进程、init进程、kthreadd进程的创建

kthreadd进程是在内核初始化start_kernel()的最后rest_init()函数中,由0号进程(swapper进程)创建了两个进程:

  • init进程(PID = 1, PPID = 0)
  • kthreadd进程(PID = 2, PPID = 0)

在start_kernel函数中调用了rest_init函数,在rest_init函数中创建了kernel_init、kthreadd内核线程:

rest_init是idle进程调用的,因此 idle进程 是所有进程的最上层ppid,即0号进程

 

noinline void __ref rest_init(void)
{
	struct task_struct *tsk;
	int pid;

	rcu_scheduler_starting();
	/*
	 * We need to spawn init first so that it obtains pid 1, however
	 * the init task will end up wanting to create kthreads, which, if
	 * we schedule it before we create kthreadd, will OOPS.
	 */
	pid = kernel_thread(kernel_init, NULL, CLONE_FS);                 //创建init进程,入参kernel_init是进程的执行体,类似java线程的run函数
	/*
	 * Pin init on the boot CPU. Task migration is not properly working
	 * until sched_init_smp() has been run. It will set the allowed
	 * CPUs for init to the non isolated CPUs.
	 */
	rcu_read_lock();
	tsk = find_task_by_pid_ns(pid, &init_pid_ns);
	set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
	rcu_read_unlock();

	numa_default_policy();
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);       //创建kthreadd进程,入参kthreadd是进程的执行体,类似java线程的run函数
	rcu_read_lock();
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	rcu_read_unlock();

	/*
	 * Enable might_sleep() and smp_processor_id() checks.
	 * They cannot be enabled earlier because with CONFIG_PREEMPTION=y
	 * kernel_thread() would trigger might_sleep() splats. With
	 * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
	 * already, but it's stuck on the kthreadd_done completion.
	 */
	system_state = SYSTEM_SCHEDULING;

	complete(&kthreadd_done);                   // 等待kthreadd创建完毕

	/*
	 * The boot idle thread must execute schedule()
	 * at least once to get things moving:
	 */
	schedule_preempt_disabled();
	/* Call into cpu_idle with preempt disabled */
	cpu_startup_entry(CPUHP_ONLINE);
}
 

3 kthreadd进程

在这里插入图片描述
kthreadd就是Linux的2号进程,这个进程在Linux内核中非常的重要,他是其他内核线程的父进程或者祖先进程(这个可以通过上面的PPID为2的进程可以看出,这些重要线程包括kworkerkblockdkhugepaged…),即其他进程都是通过2号进程创建的,2号进程轮询创建进程的任务队列,下面便慢慢来介绍下kthreadd进程。

3.1 kthreadd进程的启动

kthreadd是进程的执行体,我们看看这个进程干了啥?:

int kthreadd(void *unused)
{
	struct task_struct *tsk = current;

	/* Setup a clean context for our children to inherit. */
	set_task_comm(tsk, "kthreadd");
	ignore_signals(tsk);
	set_cpus_allowed_ptr(tsk, housekeeping_cpumask(HK_FLAG_KTHREAD));   /*允许kthreadd在任意cpu上执行*/
	set_mems_allowed(node_states[N_MEMORY]);

	current->flags |= PF_NOFREEZE;
	cgroup_init_kthreadd();

	for (;;) {
		set_current_state(TASK_INTERRUPTIBLE);
		if (list_empty(&kthread_create_list)) /* 判断内核线程链表是否为空 */
			schedule(); /* 若没有需要创建的内核线程,进行一次调度,让出cpu */
		__set_current_state(TASK_RUNNING);

		spin_lock(&kthread_create_lock);
		while (!list_empty(&kthread_create_list)) {    /*依次取出任务*/
			struct kthread_create_info *create;

			create = list_entry(kthread_create_list.next,
					    struct kthread_create_info, list);
			list_del_init(&create->list);       /*从任务列表中摘除*/
			spin_unlock(&kthread_create_lock);
                        /* 只要kthread_create_list不为空,就根据表中元素创建内核线程 */
			create_kthread(create);       //创建子进程

			spin_lock(&kthread_create_lock);
		}
		spin_unlock(&kthread_create_lock);
	}

	return 0;
}
            

从上述代码中可以看出:kthreadd进程的任务就是不断轮询,等待创建线程,如果任务队列为空,则线程主动让出cpu(调用schedule后会让出cpu,本线程会睡眠):如果不为空,则依次从任务队列中取出任务,然后创建相应的线程。如此往复,直到永远…

3.2 kthreadd进程创建子进程

从上一个章节,我们知道kthreadd进程负责轮询队列,创建子进程,那么我们看看是如何创建子进程的

创建子进程,会调用create_kthread函数,在create_kthread函数中会通过调用kernel_thread函数来创建新进程,且新进程的执行函数为kthread(类似java线程的run方法)

static void create_kthread(struct kthread_create_info *create)
{
	int pid;
 
#ifdef CONFIG_NUMA
	current->pref_node_fork = create->node;
#endif
	/* We want our own signal handler (we take no signals by default). */
	pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);/*开始创建线程,会阻塞*/
	if (pid < 0) {
		/* If user was SIGKILLed, I release the structure. */
		struct completion *done = xchg(&create->done, NULL);
 
		if (!done) {
			kfree(create);
			return;
		}
		create->result = ERR_PTR(pid);
		complete(done);
	}
}

kernel_thread接口刚才在rest_init接口中遇到过,内核就是通过kernel_thread接口创建的init进程和kthreadd进程。这里再次使用它创建新线程,新的线程执行体统一为kthead。下面我们看看kthread函数的内容:

static int kthread(void *_create)
{
	/* Copy data: it's on kthread's stack */
	struct kthread_create_info *create = _create;
	int (*threadfn)(void *data) = create->threadfn;
	void *data = create->data;
	struct completion *done;
	struct kthread *self;
	int ret;
 
	self = kzalloc(sizeof(*self), GFP_KERNEL);
	set_kthread_struct(self);
 
	/* If user was SIGKILLed, I release the structure. */
	done = xchg(&create->done, NULL);
	if (!done) {
		kfree(create);
		do_exit(-EINTR);
	}
 
	if (!self) {
		create->result = ERR_PTR(-ENOMEM);
		complete(done);
		do_exit(-ENOMEM);
	}
 
	self->data = data;
	init_completion(&self->exited);
	init_completion(&self->parked);
	current->vfork_done = &self->exited;
 
	/* OK, tell user we're spawned, wait for stop or wakeup */
	__set_current_state(TASK_UNINTERRUPTIBLE);
	create->result = current;
	complete(done);
	schedule();/*睡眠,一直。直到被唤醒*/
 
	ret = -EINTR;
	if (!test_bit(KTHREAD_SHOULD_STOP, &self->flags)) {/*唤醒后如果此线程不需要stop*/
		cgroup_kthread_ready();
		__kthread_parkme(self);
		ret = threadfn(data);/*执行指定的函数体*/
	}
	do_exit(ret);
}

从kthread函数可以看出,新线程创建成功后,会一直睡眠(使用schedule主动让出CPU并睡眠),直到有人唤醒它(wake_up_process);线程被唤醒后,并且不需要stop, 则执行指定的函数体( threadfn(data) )。

3.3 kthreadd的工作流程总结

在这里插入图片描述
上图中显示了内核创建线程的基本流程:

①某一个线程A(左上那个圈)调用kthread_create函数来创建新线程,调用后阻塞;kthread_create会将任务封装后添加到kthreadd监控的工作队列中;

②kthreadd进程检测到工作队列中有任务,则结束休眠状态,通过调用create_kthread函数创建线程,最后调用到kernel_thread --> do_fork来创建线程,且新线程执行体为kthead

③新线程创建成功后,执行kthead,kthreadd线程则继续睡眠等待创建新进程;

④线程A调用kthread_create返回后,在合适的时候通过wake_up_process(pid)来唤醒新创建的线程

⑤新创建的线程在kthead执行体中被唤醒,检测到是否需要stop,在不需要stop时,执行用户指定的线程执行体。(线程执行体发生了变化:先执行默认的kthead,然后才是用户指定的threadfn,当然也可能直接执行do_exit退出线程)

4. init进程

更多init进程作用参见 【linux】init进程的详解

init进程由idle通过kernel_thread创建,在内核空间完成初始化后,加载init程序

在这里我们就主要讲解下init进程,init进程由0进程创建,完成系统的初始化,是系统中所有其他用户进程的祖先进程

Linux中的所有进程都是由init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成后,init将变成为守护进程监视系统其他进程。

所以说init进程是Linux系统操作中不可缺少的程序之一,如果内核找不到init进程就会试着运行/bin/sh,如果运行失败,系统的启动也会失败。

4.1 init进程的启动

从前文得知,创建1号进程,传入 kernel_init方法体,我们看下做了什么:

static int __ref kernel_init(void *unused)
{
	int ret;

	kernel_init_freeable();             //kernel_init_freeable函数中就会做各种外设驱动的初始化
	/* need to finish all async __init code before freeing the memory */
	async_synchronize_full();
	kprobe_free_init_mem();
	ftrace_free_init_mem();
	free_initmem();
	mark_readonly();

	/*
	 * Kernel mappings are now finalized - update the userspace page-table
	 * to finalize PTI.
	 */
	pti_finalize();

	system_state = SYSTEM_RUNNING;
	numa_default_policy();

	rcu_end_inkernel_boot();

	do_sysctl_args();

	if (ramdisk_execute_command) {
		ret = run_init_process(ramdisk_execute_command);
		if (!ret)
			return 0;
		pr_err("Failed to execute %s (error %d)\n",
		       ramdisk_execute_command, ret);
	}

	/*
	 * We try each of these until one succeeds.
	 *
	 * The Bourne shell can be used instead of init if we are
	 * trying to recover a really broken machine.
	 */
	if (execute_command) {
		ret = run_init_process(execute_command);
		if (!ret)
			return 0;
		panic("Requested init %s failed (error %d).",
		      execute_command, ret);
	}

	if (CONFIG_DEFAULT_INIT[0] != '\0') {
		ret = run_init_process(CONFIG_DEFAULT_INIT);
		if (ret)
			pr_err("Default init %s failed (error %d)\n",
			       CONFIG_DEFAULT_INIT, ret);
		else
			return 0;
	}
/* 调用系统根目录下的init文件,会在以下几个路径中加载init进程 */
/* 在Android源码中,init进程源码的位置为:https://www.androidos.net.cn/android/10.0.0_r6/xref/system/core/init/init.cpp */
	if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;        

	panic("No working init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/admin-guide/init.rst for guidance.");
}

  • kernel_init_freeable函数中就会做各种外设驱动的初始化
  • 最主要的工作就是调用 init可以执行文件,即开机自动运行的程序,通过Init配置文件在约定的路径下,会被自动读取

至此1号进程就完美的创建成功了,而且也成功执行了init可执行文件

4. 总结

总结:

  • linux启动的第一个进程是0号进程,是静态创建的
  • 在0号进程启动后会接连创建两个进程,分别是1号进程和2和进程。
  • 1号进程最终会去调用可init可执行文件,init进程最终会去创建所有的应用进程。
  • 2号进程会在内核中负责创建所有的内核线程
  • 所以说0号进程是1号和2号进程的父进程;1号进程是所有用户态进程的父进程;2号进程是所有内核线程的父进程。

我们通过ps命令就可以详细的观察到这一现象:

root@ubuntu:zhuxl$ ps -eF
UID         PID   PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root          1      0  0 56317  5936   2 Feb16 ?        00:00:04 /sbin/init
root          2      0  0     0     0   1 Feb16 ?        00:00:00 [kthreadd]

上面很清晰的显示:PID=1的进程是init,PID=2的进程是kthreadd。而他们俩的父进程PPID=0,也就是0号进程。


再来看下,所有内核线性的PPI=2, 也就是所有内核线性的父进程都是kthreadd进程。

UID         PID   PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root          4      2  0     0     0   0 Feb16 ?        00:00:00 [kworker/0:0H]
root          6      2  0     0     0   0 Feb16 ?        00:00:00 [mm_percpu_wq]
root          7      2  0     0     0   0 Feb16 ?        00:00:10 [ksoftirqd/0]
root          8      2  0     0     0   1 Feb16 ?        00:02:11 [rcu_sched]
root          9      2  0     0     0   0 Feb16 ?        00:00:00 [rcu_bh]
root         10      2  0     0     0   0 Feb16 ?        00:00:00 [migration/0]
root         11      2  0     0     0   0 Feb16 ?        00:00:00 [watchdog/0]
root         12      2  0     0     0   0 Feb16 ?        00:00:00 [cpuhp/0]
root         13      2  0     0     0   1 Feb16 ?        00:00:00 [cpuhp/1]
root         14      2  0     0     0   1 Feb16 ?        00:00:00 [watchdog/1]
root         15      2  0     0     0   1 Feb16 ?        00:00:00 [migration/1]
root         16      2  0     0     0   1 Feb16 ?        00:00:11 [ksoftirqd/1]
root         18      2  0     0     0   1 Feb16 ?        00:00:00 [kworker/1:0H]
root         19      2  0     0     0   2 Feb16 ?        00:00:00 [cpuhp/2]
root         20      2  0     0     0   2 Feb16 ?        00:00:00 [watchdog/2]
root         21      2  0     0     0   2 Feb16 ?        00:00:00 [migration/2]
root         22      2  0     0     0   2 Feb16 ?        00:00:11 [ksoftirqd/2]
root         24      2  0     0     0   2 Feb16 ?        00:00:00 [kworker/2:0H]

UID         PID   PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root        362      1  0 21574  6136   2 Feb16 ?        00:00:03 /lib/systemd/systemd-journald
root        375      1  0 11906  2760   3 Feb16 ?        00:00:01 /lib/systemd/systemd-udevd
systemd+    417      1  0 17807  2116   3 Feb16 ?        00:00:02 /lib/systemd/systemd-resolved
systemd+    420      1  0 35997   788   3 Feb16 ?        00:00:00 /lib/systemd/systemd-timesyncd
root        487      1  0 43072  6060   0 Feb16 ?        00:00:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
root        489      1  0  8268  2036   2 Feb16 ?        00:00:00 /usr/sbin/cron -f
root        490      1  0  1138   548   0 Feb16 ?        00:00:01 /usr/sbin/acpid
root        491      1  0 106816 3284   1 Feb16 ?        00:00:00 /usr/sbin/ModemManager
root        506      1  0 27628  2132   2 Feb16 ?        00:00:01 /usr/sbin/irqbalance --foreground

所有用户态的进程的父进程PPID=1,也就是1号进程都是他们的父进程。

参考

Linux中的特殊进程:idle进程、init进程、kthreadd进程
Linux内核学习之2号进程kthreadd
Linux0号进程,1号进程,2号进程

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

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

相关文章

【作者踩坑总结0错版】vscode配置codeql-MacBook(M1/M2芯片-arm)

文章目录 前言一、codeql是什么二、安装必备工具2.1、安装codeql-cli2.2、安装codeql仓库2.3、桌面新建文件夹2.4、安装vscode 三、配置codeql环境变量四、vscode配置codeql插件4.1、下载测试靶场4.2、生成数据库文件&#xff08;最难最多bug&#xff09;4.3、vscode配置ql数据…

[入门必看]数据结构5.3:二叉树的遍历和线索二叉树

[入门必看]数据结构5.3&#xff1a;二叉树的遍历和线索二叉树 第五章 树与二叉树5.3 二叉树的遍历和线索二叉树知识总览5.3.1_1 二叉树的先中后序遍历5.3.1_2 二叉树的层次遍历5.3.1_3 由遍历序列构造二叉树5.3.2_1 线索二叉树的概念5.3.2_2 二叉树的线索化5.3.2_3 在线索二叉树…

服务(第十九篇)mysql的备份和恢复

备份的类型&#xff1a; ①物理备份&#xff1a;备份数据库的物理文件&#xff0c;数据文件和日志文件&#xff1b; 冷备份&#xff1a;关闭数据库进行的&#xff1b; 热备份&#xff1a;数据库处于运行中状态&#xff0c;依赖于日志文件&#xff1b; 温备份&#xff1a;数…

Cisco NX-OS Software Release 10.3(3)F - 网络操作系统软件

Cisco NX-OS Software Release 10.3(3)F - 网络操作系统软件 请访问原文链接&#xff1a;https://sysin.org/blog/cisco-nx-os-10/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org Cisco NX-OS Cisco NX-OS 操作系统助力网络紧…

Golang中map基础

目录 map类型 声明和初始化 元素操作 大小和容量 并发问题 实现原理 注意 map类型 Go语言中的map类型是一种key-value&#xff08;键值对&#xff09;结构&#xff0c;用于存储一组无序的数据&#xff0c;其中键是唯一的&#xff0c;值可以相同或者不同。在Go语言的程序…

button 设置外观

导入图片 设置拉伸 设置按钮的FlatStyle属性为Flat 设置透明背景

先到先得先学,2023最新国内大厂最新Java面试八股文总结

“铜三铁四”接近尾声&#xff0c;身边被迫“毕业”或者主动在这个时间点跳槽的大佬基本该找到工作的都找到工作了&#xff0c;找不到的也大多数都已经躺平了&#xff08;手动狗头&#xff09;&#xff0c;只剩一群“45度人”&#xff08;卷不动&#xff0c;躺不平的人&#xf…

用于跨中心多边形分割的任务相关特征补充

文章目录 Task-Relevant Feature Replenishment for Cross-Centre Polyp Segmentation摘要本文方法Domain-Invariant Feature Decomposition (DIFD)Task-Relevant Feature ReplenishmentPolyp-Aware Adversarial Learning (PAAL)总损失函数 结果 Task-Relevant Feature Repleni…

AI翻译工具分享

LanguageX简介 冠军翻译引擎交互式翻译,为专业译者而生的新一代译者工具 自2017年神经网络机器翻译流行以来,“机器翻译替代人工翻译”的观点屡见不鲜。作为一个技术和语言背景结合的团队,LanguageX认为语言服务是高度复杂、需要创意的智力活动,并肩负着人类沟通的重要使命。La…

机器学习之k-means聚类二、啤酒聚类实现

文章目录 一、环境开发说明二、基于前篇理论实现1. 数据说明2. 具体实现流程a. 对数据进行标准化处理b. 使用手肘法进行K值得选择c. 算法实现&#xff0c;d.将聚类结果进行可视化e. 计算轮廓系数f. 轮廓系数可视化 三、 文中的完整代码四、不调包实现五、参考文献 一、环境开发…

MySQL-图形化界面工具 (下

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️树高千尺&#xff0c;落叶归根人生不易&…

ip route 和 route 命令

ip route 属于 iproute2 工具包&#xff1b;route 属于 net-tools 工具包&#xff1b;目前 ip route 使用较为广泛&#xff1b; 两个工具包的介绍请见 “iproute2 和 net-tools 介绍” 文章。 ip route命令介绍 ip route命令用于管理静态路由表。 linux 系统中&#xff0c;可…

【GigaGAN论文精读】Scaling up GANs for Text-to-Image Synthesis

【GigaGAN论文精读】Scaling up GANs for Text-to-Image Synthesis 0、前言Abstract1. Introduction&#xff08;图放在文末&#xff09;2. Related Works2.1Text-to-image synthesis.2.2GAN-based image synthesis.2.3Super-resolution for large-scale text-to-image models.…

【Java|基础篇】面向对象三大特性之封装

文章目录 1.前言2.什么是封装3.如何更好的封装4.封装的好处5.总结 1.前言 面向对象有三个特性:封装,继承和多态,本文主要讲解封装. 封装是面向对象编程的基本原则之一&#xff0c;它可以提高程序的安全性、稳定性、可维护性和可扩展性&#xff0c;减少了代码的冗余&#xff0c…

【框架源码】Spring源码核心注解Conditional和应用

1.什么是Conditional注解 Conditional来源于spring-context包下的一个注解。通过Conditional配置一些条件判断&#xff0c;当所有条件都满足时&#xff0c;被该Conditional注解标注的目标才会被Spring处理。 例如根据当前环境、系统属性、配置文件等条件来决定是否注册某个Bea…

股票量价关系基础知识3

成交量变化的表现形式 成交量变化的表现形式主要有以下几种 一、缩量 一般界定当日成交量低于前一交易日成交量10%以上为缩量。 缩量往往发生在上升途中的回调末期、上涨末期以及股价下跌的中后期。一般下跌趋势中的缩量阶段&#xff0c;往往意味着股价仍未见底&#xff0c;不能…

服务攻防-中间件安全CVE复现IISApacheTomcatNginx漏洞复现

目录 一、导图 二、ISS漏洞 中间件介绍> 1、短文件 2、文件解析 3、HTTP.SYS 4、cve-2017-7269 三、Nignx漏洞 中间件介绍> 1、后缀解析漏洞 2、cve-2013-4547 3、cve-2021-23017 无 EXP 4、cve-2017-7529 意义不大 四、Apache漏洞 中间件介绍> 1、漏…

AcWing算法提高课-1.3.5买书

宣传一下算法提高课整理 <— CSDN个人主页&#xff1a;更好的阅读体验 <— 本题链接&#xff08;AcWing&#xff09; 点这里 题目描述 小明手里有n元钱全部用来买书&#xff0c;书的价格为10元&#xff0c;20元&#xff0c;50元&#xff0c;100元。 问小明有多少种买…

【Python--读写模式全解】

读写模式 读写模式语法读取写入追加 小结 读写模式 open() 函数常用形式是接收两个参数&#xff1a;文件名(file)和模式(mode)。 # 读写文件最好用 with...open...操作&#xff0c;这样最安全 # 而且还不需要关闭文件 with open(path,r)as f:f.read() # 一次读取整个文件&…

如何调用API数据接口来获取想要的数据

API&#xff08;Application Programming Interface&#xff09;是连接不同软件和服务的桥梁&#xff0c;允许不同的应用程序之间共享和交换数据。API接口被广泛应用于各种场景&#xff0c;如&#xff1a; 1.移动应用程序&#xff1a;许多移动应用程序使用API接口与服务器通信…