深度剖析进程概念与进程状态

文章目录

  • 1. 前言
  • 2. 什么是进程
    • 2.1 进程概念
    • 2.2 进程描述——PCB
  • 3. 进程的一些基本操作
    • 3.1 查看进程
    • 3.2 结束进程
    • 3.3 通过系统调用获取进程标示符
    • 3.4 通过系统调用创建子进程
  • 4. 进程状态
    • 4.1 普适的操作系统层面
    • 4.2 具体Linux操作系统层面
  • 5. 两种特殊的进程
    • 5.1 僵尸进程
    • 5.2 孤儿进程
  • 6. 进程优先级
  • 7. 进程的其他概念
  • 8. 进程切换


1. 前言

在学习上一节冯诺依曼体系结构与操作系统时,我们得出结论:管理的本质是 先描述再组织,在此基础上本篇文章将带你从以下几个方面来深入学习进程概念与进程状态:

  1. 进程是什么
  2. PCB是什么
  3. 进程的一些基本操作
  4. 进程状态
  5. 特殊的进程
  6. 进程的优先级
  7. 进程的其他概念
  8. 进程切换

2. 什么是进程

2.1 进程概念

在通常的计算机书籍或者课本中对进程概念的描述是这样的:进程就是被加载到内存中的程序,或者被运行起来的程序就叫做进程

如何理解呢?

我们知道,为了提高计算机的整体效率,在数据层面,CPU不会直接和外设打交道,而只会和内存进行交互;同样,外设也只和内存交互;我们平时编写的C/C++代码经过编译链接后形成的二进制可执行程序其实本质上就是存放在磁盘上的一个文件;所以如果我们要运行这个程序,就必须先将其加载到内存,因为CPU要从内存中读取程序中的代码和数据进行运算。

但是同时,在程序加载进内存后,操作系统要对程序进行管理,而从上一节的学习中我们知道,对程序的管理本质上是对程序数据的管理,管理的方法就是先描述,再组织

所以操作系统会从这些程序中抽象出共有的属性来构建一个结构体,然后为每一个进程都创建一个结构体的对象,最后再将所有的结构体对象使用某一种高效的数据结构组织起来。至此,对某个进程的管理就转变成了对数据结构中某一个节点的管理。在操作系统中,这个用于描述和组织进程的结构体被称为进程控制块(PCB)

2.2 进程描述——PCB

进程控制块PCB (process control block):操作系统中用于描述进程的工具,其中包含的是进程属性的集合

Linux操作系统下的PCB是task_struct,它是Linux内核的一种数据结构,其内容可以分为如下几类:

  • 标示符:描述本进程的唯一标示符,用来区别其他进程。
  • 状态:任务状态,退出代码,退出信号等。
  • 优先级:相对于其他进程的优先级。
  • 程序计数器:程序中即将被执行的下一条指令的地址。
  • 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
  • 上下文数据:进程执行时处理器的寄存器中的数据。
  • I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 其他信息。

抽象出来可以用如下结构体来表示(假设task_struct使用链表进行组织):

struct task_struct {     //进程的所有属性... ...//进程对应的代码和数据的地址... ...//下一个进程的地址struct task_struct* next;
};

注:关于task_struct的详细介绍,即其中包含的具体进程属性,可以参考这篇文章:Linux中进程控制块PCB-------task_struct结构体结构 。

当了解了task_struct之后,我们就应该从操作系统内核的观点来看待进程了:进程 = PCB + 进程对应的代码数据

3. 进程的一些基本操作

3.1 查看进程

  1. ps axj指令配合grep和管道查看指定进程:

    在这里插入图片描述

  2. /proc系统文件夹中查看所有进程:

    在这里插入图片描述

3.2 结束进程

  1. 使用Ctrl + c结束进程:

    在这里插入图片描述

  2. 使用kill -9结束进程:

    在这里插入图片描述

3.3 通过系统调用获取进程标示符

我们可以通过使用操作系统给我们提供的系统调用接口getpid()getppid()来获取进程id和父进程id(进程ID是一个进程的唯一标示):

在这里插入图片描述

注:关于函数的返回值pid_t,大家把它当作int看待即可,打印的时候也使用%d

//test.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main()
{while(1){printf("我是一个进程,我的ID是:%d,我的父进程ID是:%d\n", getpid(), getppid());sleep(1);}return 0;
}

在这里插入图片描述

可以看到,我们通过getpid()getppid()函数得到的值的确是我们进程对应的id。同时,我们发现test进程的父进程是bash,即shell外壳,shell为了防止自身崩溃,并不会自己去执行指令,而是会派生子进程去执行。

同一个程序重新被运行时它的进程id可能与之前不一样,因为它的代码和数据需要重新从磁盘中加载;但是它的父进程id一定是一样的,因为它们都是通过bash来执行。

3.4 通过系统调用创建子进程

我们可以通过系统调用接口 fork 来创建子进程:

在这里插入图片描述

//test.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main()
{pid_t id = fork();if(id == 0){while(1){printf("子进程,pid:%d, ppid:%d, id:%d\n", getpid(), getppid(), id);sleep(1);}}else if(id > 0){while(1){printf("父进程,pid:%d, ppid:%d, id:%d\n", getpid(), getppid(), id);sleep(1);}}else {printf("子进程创建失败!\n");}return 0;
}

在这里插入图片描述

可以看到,子进程的ppid是父进程的pid,父进程的ppidbash。同时,对于父进程,fork函数返回子进程的pid;对于子进程,fork返回0。

4. 进程状态

4.1 普适的操作系统层面

进程状态是什么?

一个程序被加载到内存变成进程之后,操作系统要对该进程进行管理,即为其创建对应的PCB对象。而进程状态,本质上就是PCB内部的一个整型变量,不同的整型值就对应不同的进程状态

在普适的操作系统层面,即站在操作系统学科的角度来说,进程状态可能有如下几种:运行、挂起、阻塞、新建、就绪、等待、挂机、死亡。其中最重要也是最难理解的几种状态分别是:运行、阻塞、挂起

运行状态

操作系统为了合理分配CPU以及各种硬件资源,也为了更好的调度各个进程,会为CPU创建一个进程队列,为每一个硬件都创建一个等待队列。让某一个进程处于运行状态本质上就是将该进程对应的PCB放入CPU的运行队列中排队,然后再将PCB中维护进程状态的变量修改为相应的值,比如0。

因为进程PCB里面有进程的各种属性,以及进程对应的代码和数据的地址,所以CPU从运行队列中取出PCB后,可以根据该PCB来得到进程的各种数据和指令,然后执行相应运算。

所以进程处于运行状态并不一定意味着该进程此刻正在被运行,只要该进程处于CPU的运行队列中即可。(注:CPU是纳秒级的芯片,运算速度非常快,所以只要进程处于CPU的运行队列中,我们就可以认为该进程正在被运行)

阻塞状态

和CPU一样,我们计算机中的各种硬件也是十分有限的,但是需要使用这些硬件资源的进程却有很多,比如很多进程都需要向磁盘中写入数据,又或者要通过网卡发送数据。但是一个磁盘或者一个网卡在同一个时刻只能为一个进程提供服务,那么如果此时有其他运行中的进程需要使用该硬件资源,操作系统就会将该进程的PCB放入硬件的等待队列中,等待硬件来为我提供服务。

上面这种由于访问某种硬件需要进行等待的状态就被称为阻塞状态,阻塞状态本质上就是将进程的PCB从CPU的运行队列中剥离出来,放入硬件的等待队列中,然后将PCB中维护进程状态的变量修改为相应的值,比如1。待该进程获得对应的对应的硬件资源以后,再将该进程放入CPU的运行队列中

注:并不是只有等待硬件资源进程才会处于阻塞状态,一个进程等待另一个进程就绪、一个进程等待某种软件资源就绪等都会处于阻塞状态。

挂起状态

上面我们学习了阻塞状态,处于阻塞状态的进程由于需要等待某种资源,所以它对应的代码和数据在短期内并不会被执行,此时它们仍存在在内存中就相当于浪费了内存资源。而如果当前操作系统处于高IO的情况下,内存空间不足,操作系统就会选择将这些处于阻塞状态的进程对应的代码和数据拷贝一份存放到磁盘中,然后释放内存中那一份,从而节省出内存空间

上面这种由于内存空间不足,操作系统将在等待资源的进程对应的代码数据放到磁盘中以节省内存空间的状态就被称为挂起状态挂起状态不会移动进程的PCB,只会移动进程对应的代码和数据

注:挂起进程并不是释放进程,因为该进程对应的PCB仍然处于某硬件的等待队列中,当该进程获得对应的资源以后,操作系统仍然可以将该进程对应的代码和数据从磁盘加载到内存中来继续运行,其本质是对内存数据的唤入唤出。同时阻塞不一定挂起,挂起也不一定阻塞,也可能是新建挂起、就绪挂起,甚至是运行挂起。

总结:进程状态改变的本质是进程对应的PCB (task_struct对象) 处于不同设备的运行队列/等待队列中。

4.2 具体Linux操作系统层面

上面我们谈到的都是理论上的操作系统中进程的状态,下面我们来学习具体Linux操作系统中进程的状态。

Linux内核源代码中对进程状态的定义如下:

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};

可以看到,Linux中进程一共有七种状态,分别是运行、睡眠、深度睡眠 (磁盘休眠)、暂停、追踪暂停、死亡、僵尸。

运行状态(R)

运行状态即进程的PCB位于CPU的运行队列中:

在这里插入图片描述

睡眠状态(S)

Linux下的睡眠状态其实就是我们上面所说的阻塞状态,进程需要等待某种资源:

在这里插入图片描述

注:我们使用ps axj指令查看进程状态只能查看进程某一时刻的状态,而外设的速度是要远远低于CPU的,所以我们可以发现,虽然test也在执行代码,但是我们每次查询时进程基本都处于阻塞状态,因为进程99%的时间都在等待硬件资源就绪,只有1%的时间在执行打印代码。

深度睡眠状态(D)

上面我们提到,当内存空间不足的时候,操作系统会将一部分进程挂起来节省资源。但是如果内存空间严重不足,挂起已经解决不了问题的时候,操作系统就会主动杀掉某些进程。

那么这里就出现了一个问题,万一操作系统把某些非常重要的进程杀掉了怎么办?比如,当前有一个进程需要向磁盘写入一批数据,这批数据是10万名用户一年的转账记录,非常重要。该进程访问磁盘,让磁盘帮它写入数据,在磁盘写数据的时间段内,该进程是属于阻塞状态的,因为它要等待磁盘返回给它一个结果,即是否写入成功。而一旦该进程被操作系统杀掉,且恰好磁盘写入失败了,磁盘将写入结果反馈给该进程发现无人应答,磁盘就只能将该部分数据丢弃然后为其他进程提供服务。此时,这部分十分重要的用户数据就丢了。

为了防止这种情况的发生,Linux设计出了深度睡眠(D)状态,处于深度睡眠状态的进程既不能被用户杀掉,也不能被操作系统杀掉,只能通过断电,或者等待进程自己醒来。

注:深度睡眠一般只会在高IO的情况发生下,且如果操作系统中存在多个深度睡眠状态的程序,那么说明该操作系统也即将崩溃了。

暂停状态(T)

暂停状态其实也属于阻塞状态的一种,我们可以使用kill命名,指定-19选项来让一个进程从运行状态变为暂停状态:

在这里插入图片描述

在这里插入图片描述

当然,我们也可以使用kill -18让一个处于暂停状态的进程恢复运行:

在这里插入图片描述

在这里我们注意到了一个细节:我们将test暂停或者continue之后,进程状态后面的+号消失了。其实,进程状态后面的+号代表着一个进程是前台进程,没有+号就代表是后台进程。对于前台进程,我们可以使用Ctrl + c将其终止,也可以用kill命令杀死它;但是对于后台进程来说,我们只能通过kill命令来杀死它。

追踪暂停状态(t)

追踪暂停状态是一种特殊的暂停状态,进程处于此状态表示该进程正在被追踪,比如gdb调试进程:

在这里插入图片描述

死亡状态(X)

死亡状态代表着一个进程结束运行,该进程对应的PCB以及代码和数据全部被操作系统回收。

僵尸状态(Z)

我们创建一个进程的目的是为了让其帮我们完成某种任务,而既然是完成任务,进程在结束前就应该返回任务执行的结果,供父进程或者操作系统读取。

所以,一个进程在退出的时候,不能立即释放全部资源。对于进程的代码和数据,操作系统可以释放,因为该进程已经不会再被执行了,但是该进程的PCB应该保留,因为PCB中存放着该进程的各种状态代码,特别是退出状态代码。

僵尸状态就是进程在退出时等待父进程或者操作系统来读取退出状态代码,然后释放PCB的一种状态。

总结:可以看到,具体的Linux操作系统下的进程状态和普适的操作系统学科上进程的状态是不同的,比如Linux操作系统没有阻塞和挂起状态,阻塞状态通过睡眠、深度睡眠、暂停、追踪暂停等状态表现出来,而进程处于这些状态时是否会被调整为挂起状态,用户是不可得知的,因为操作系统没必要将挂起状态暴露给用户,用户也不关心一个进程是否会处于挂起状态。

5. 两种特殊的进程

5.1 僵尸进程

什么是僵尸进程?

上面我们提到一个进程的资源在被全部释放之前,需要由父进程或者操作系统来读取退出状态代码,而如果父进程不读取子进程的退出状态代码,该进程的PCB就一直得不到释放,此时该进程就会变成僵尸进程。

举个例子说明:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{int id = fork();if(id > 0){while(1){printf("我是父进程,pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);}}else if(id == 0){while(1){printf("我是子进程,pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);}}else {perror("fork fail");exit(-1);}return 0;
}

在这里插入图片描述

可以看到,当我们kill掉子进程19577之后,由于父进程没有对子进程的退出状态代码进行读取,所以子进程变成了Z状态,并且子进程后面还提示了defunct(失效的,不再使用的),此时,如果父进程一直不对子进程进行读取,那么子进程就会变成僵尸进程。

僵尸进程有如下危害

如果父进程或者操作系统一直不对子进程的退出状态进行读取,那么子进程的退出状态就将一直被维持下去。维护退出状态本身就是要用数据维护,也属于进程基本信息,所以如果进程一直处于退出状态,进程对应的PCB(task_struct)就将一直存在,不会被释放,会造成内存资源的浪费

5.2 孤儿进程

什么是孤儿进程?

孤儿进程是指父进程提前退出后,子进程被操作系统领养的一种情况,被操作系统领养的进程就被称为孤儿进程。

在这里插入图片描述

在这里插入图片描述

这里还有两个细节:第一,父进程退出后并没有变成Z状态,因为父进程的父进程是bashbash会读取父进程的退出状态;第二,子进程被领养后变成了后台进程

6. 进程优先级

什么是优先级?

优先级和权限不同,权限决定的是一件事情能不能做,优先级是在权限允许的前提下,该事情先做还是后做。

为什么要有优先级?

资源是有限的,内存中有很多进程都要占用资源,但是资源是有限的,所以我们需要指定优先级来合理的分配资源。

Linux中优先级的特点:

Linux中优先级的表示与维护通过两个变量PRI(priority)NI(nice)来完成,每个进程默认的PRI都是80,NI都是0。我们可以通过修改NI的值来调整进程的优先级,NI的改动范围为[-20, 19]PRINI的和越小,进程的优先级就越高。

Linux中我们通过ps -l来查看进程的优先级:

在这里插入图片描述

我们也可以通过如下步骤来修改进程优先级(将进程优先级调高可能需要sudo权限):

输入top --> 输入r --> 输入进程id --> 输入NI值

在这里插入图片描述

最后,虽然我们可以通过修改NI值来调整进程优先级,但是我们一般都不会这样做,因为效果不大。

7. 进程的其他概念

进程还有一些其他概念:

竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。(进程为了高效完成任务,更合理竞争相关资源,便具有了优先级)
独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰。(每个进程的PCB以及代码数据都是独立的,一个进程的死亡不会影响其他进程,包括父子进程,子进程崩溃并不会影响父进程)
并行:多个进程在多个CPU下分别、同时进行运行,这称之为并行。
并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。(我们用的电脑一般都是并发,只有一个CPU)

8. 进程切换

我们的笔记本只有一个CPU,代表着同一时刻只能有一个进程正在被运行,但是我们平时在使用电脑的时候,同一时间通常会运行着很多程序,并且这些程序都能被正常运行,这其实是进程切换的效果。

进程切换:CPU同一时刻只能运行一个进程,但是CPU的运算速度非常快,所以位于CPU运行队列中的每一个进程都只运行一个时间片,每个进程运行完一个时间片后被都被放到运行队列尾部,等待下次运行,这样使得在一个时间段中多个进程都能被运行。

上下文保护与上下文恢复

CPU在进行进程切换时要进行上下文保护与上下文恢复:

把临时数据转存到操作系统的行为叫做上下文保护,把临时数据写回寄存器内的行为叫做上下文恢复。

我们的进程在运行时会产生非常多的临时数据,同时CPU中存在一套寄存器硬件,当进程运行时,进程的PCB会被放入CPU内的寄存器中,此时CPU就可以通过进程PCB得到进程代码数据的地址,CPU在运行进程时所产生的大量的临时数据也都会被保存在寄存器中

那么,我们在进行进程切换时需要进行进程的上下文保护与上下文恢复,即进程停止运行时将寄存器里面的数据保存起来,进程重新运行时将保存的数据再放入到寄存器中;以便我们能够接着上次运行的地方接着运行

图解:

在这里插入图片描述

注:CPU寄存器硬件被所有进程共享,但是当CPU在具体运行某一进程时,CPU寄存器中的数据只属于该进程。同时,我们进行上下文保护时保存的是寄存器中的数据,而不是寄存器硬件。

拓展学习:深入理解Linux内核进程的管理与调度


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

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

相关文章

Linux中的磁盘分析工具ncdu

2024年5月14日&#xff0c;周二上午 概述 ncdu 是一个基于文本的用户界面磁盘使用情况分析工具。它可以在终端中快速扫描目录&#xff0c;并统计该目录下的文件和文件夹的磁盘使用情况&#xff0c;以交互友好的方式呈现给用户。 安装 在 Debian/Ubuntu 系统下&#xff0c;可…

算法:滑动窗口题目练习

目录 题目一&#xff1a;长度最小的子数组 题目二&#xff1a;无重复字符的最长子串 题目三&#xff1a;最大连续 1 的个数III 题目四&#xff1a;将 x 减到 0 的最小操作数 题目五&#xff1a;水果成篮 题目六&#xff1a;找到字符串中所有字母异位词 题目七&#xff1a…

Java modbus 实现RTU串口作为slave(服务端)读写数据

这里要了解下modbus的RTU和TCP 的几个名称关系&#xff1a; Modbus/RTU&#xff1a;主站 和从站 关系 Modbus/TCP&#xff1a;客户端和服务端关系 关系 主站主动找从站读写数据 客户端主动找服务端读写数据 所以当使用Modbus/TCP时&#xff0c;主站一般作为客户端&#xff…

树莓派发送指令控制FPGA板子上的流水灯程序

文章目录 前言一、树莓派简介二、整体实现步骤三、树莓派设置四、树莓派串口代码五、Verilog代码5.1 串口接收模块5.2 流水灯模块 六、quartus引脚绑定七、 运行效果总结参考 前言 ​ 本次实验的目的是通过树莓派和FPGA之间的串口通信&#xff0c;控制FPGA开发板上的小灯。实验…

Excel常用操作

计算支付成功率 使用公式 ROUND(B2/C2,4)*100&"%" 字符串拼接 将A1-A10的数字用英文逗号拼接 TEXTJOIN(",",TRUE,A1:A10) 将A1-A10中大于5的数字用英文逗号拼接 ARRAYFORMULA(TEXTJOIN(",",TRUE,IF(A1:A10>5,A1:A10,"")…

未来想从事营销策划类的工作,需要怎么学习?

从事营销策划类的工作&#xff0c;提升和学习主要从以下三个方面&#xff1a; 一、营销底层逻辑的搭建 二、营销系统知识的构建 三、大量营销案例的积累 营销入门&#xff0c;其实大多数人一直都在入门的道路上&#xff0c;每个人都是终身学习者。虽然从事营销工作十年多了…

2024年5月中,AITOP100平台活动专区迎来六场AI大赛盛事!

AITOP100平台的活动专区在2024年5月中旬更新的6场AI大赛来了&#xff01; 随着人工智能技术的飞速发展&#xff0c;AI设计已经成为了创新与创意的新领域。2024年5月中旬&#xff0c;由腾讯研究院、剪映、站酷等互联网大厂主办的6场AI设计大赛震撼来袭&#xff0c;为广大AI设计…

【数据分析面试】43.寻找给小费最多的客人(Python:字典用法)

题目&#xff1a; 寻找给小费最多的客人 &#xff08;Python) 给定两个非空列表user_ids和tips&#xff0c;编写一个名为most_tips的函数&#xff0c;用于找到给小费最多的客户。 示例&#xff1a; 输入&#xff1a; user_ids [103, 105, 105, 107, 106, 103, 102, 108, 1…

短剧看剧系统,当前互联网热门项目工具系统模板。

目录 揭秘爆款神器&#xff1a;短剧看剧系统&#xff0c;让你的内容火遍全网&#xff01; 一、短剧看剧系统&#xff1a;一站式解决方案 二、灵活定价&#xff0c;实现收益最大化 三、高效管理&#xff0c;团队协作更轻松 四、数据驱动&#xff0c;精准把握市场动态 五、智…

设置linux终端用户输入空闲一段时间后就自动断开(linux终端超时自动断开)

在 /etc/profile 中加入TMOUT变量即可。 在文件的最后追加以下两行 export TMOUT600 # 600秒内无操作就断开。 readonly TMOUT # 将变量设置为只读&#xff0c;防止用户更改如图

企业计算机服务器中了rmallox勒索病毒怎么解密,rmallox勒索病毒解密工具流程

在当今数字化时代&#xff0c;越来越多的企业依赖计算机服务器进行办公开展业务&#xff0c;计算机服务器犹如企业的心脏&#xff0c;能够为企业存储许多重要的核心信息&#xff0c;帮助企业有效的开展各项工作业务&#xff0c;提高企业的生产效果&#xff0c;但网络是一把双刃…

springMVC基础使用(示例)

maven依赖&#xff08;javax.servlet-api版本与spring-webmvc班恩要匹配不然会报java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRespons&#xff09;&#xff1a; <dependencies><dependency><groupId>javax.servlet</groupId><arti…

CAN模块开发问题概述

问题一 问题描述 工作环境&#xff1a;ECU外接canoe 操作&#xff1a;使用CANoe模拟发送NM报文&#xff0c;然后停发或者断开CANoe 现象&#xff1a;程序跑死&#xff0c;调用call stack查看压栈情况如下图所示 定位代码如下图所示。可见是由于CAN模块在设置Controller状态时…

计算机毕业设计 | vue+springboot调查问卷管理系统(附源码)

1&#xff0c;研究目的 在进入21世纪以后&#xff0c;互联网得到了蓬勃的发展&#xff0c;电子问卷调查也开始逐渐流行起来。传统纸质问卷和电子问卷相比较后&#xff0c;传统问卷还存在很多弊端&#xff1a; 问卷分发起来比较困难&#xff0c;并且分发试卷耗费大量的金钱和时…

网络安全法中关于网络信息的保护和监管,有哪些规定?

网络安全法作为我们数字时代的重要法律保障&#xff0c;对于网络信息的保护和监管有着明确且详细的规定。这些规定不仅体现了国家对于网络安全的重视&#xff0c;也为我们每个人在数字世界中提供了坚实的法律屏障。 首先&#xff0c;我们来看一个关于网络运营者主体责任的案例。…

uniapp开发安卓app使用文字转语音技术

在 UniApp 开发安卓应用时&#xff0c;要实现文字转语音&#xff08;Text-to-Speech, TTS&#xff09;技术&#xff0c;你可以利用 UniApp 的跨平台能力结合原生模块或第三方服务来实现。以下是一些建议的步骤和方法&#xff1a; 1. 使用 UniApp 原生模块&#xff08;如果支持…

【架构-17】通信系统架构设计理论

通信系统网络架构 1. 局域网网络架构 拓扑结构&#xff1a;星型、总线型、环型、树型。 网络架构&#xff1a;单核心架构&#xff08;结构简单&#xff0c;地理范围受限&#xff09;、双核心架构&#xff08;网络拓扑结构可靠&#xff0c;投资较单核高&#xff09;、环型架构…

更高效的数据交互实现丨 DolphinDB Arrow 插件使用教程

Apache Arrow 是一种跨语言的内存数据交换格式&#xff0c;旨在为用户提供高效的数据结构&#xff0c;以实现在不同的数据处理系统之间共享数据而无需进行复制。它由 Apache 软件基金会开发和维护&#xff0c;目前已经成为许多大型数据处理和分析框架的核心组件之一。在分布式框…

收藏:如何轻松建立CRM系统的帮助中心

大家好&#xff0c;今天咱们来聊聊怎么给公司的CRM系统建个帮助中心。为什么CRM系统需要建立帮助中心呢&#xff1f;很简单&#xff0c;就是为了让客户、员工在遇到问题时能有个快速找到答案的地方&#xff0c;提升咱们的服务质量和内部工作效率。 一、为什么需要建立CRM系统的…

【SQL每日一练】获取PADS公司用户名称和各职业总数并根据格式输出

文章目录 题目一、解析二、题解1.MySQL 题目 生成以下两个结果集&#xff1a; 1、查询 OCCUPATIONS 表中所有名字&#xff0c;紧跟每个职业的第一个字母作为括号&#xff08;即&#xff1a;括在括号中&#xff09;&#xff0c;并按名字顺序排序。例如&#xff1a;AnActorName…