【从浅学到熟知Linux】进程控制上篇=>进程创建、进程终止与进程等待(含_exit与exit的区别、fork函数详解、wait与waitpid详解)

在这里插入图片描述

🏠关于专栏:Linux的浅学到熟知专栏用于记录Linux系统编程、网络编程等内容。
🎯每天努力一点点,技术变化看得见

文章目录

  • 进程创建
    • fork函数
    • 写时拷贝
  • 进程退出
    • 进程退出操作系统做了什么?
    • 进程退出场景
    • 进程退出的常见方法
      • 正常终止与异常终止
      • 缓冲区的概念
      • exit与_exit的区别
  • 进程等待
    • 进程等待的必要性
    • 进程等待方法
      • wait方法
      • waitpid方法
    • wait/waitpid获取子进程信息原理


进程创建

fork函数

创建子进程时,需要使用系统调用函数fork。创建的新进程为子进程,而原进程为父进程。
在这里插入图片描述
该函数在子进程创建成功时,给父进程返回子进程pid,给子进程返回0;创建失败时,给父进程返回-1。
在这里插入图片描述
下面我们使用fork函数来创建子进程↓↓↓

#include <stdio.h>
#include <assert.h>
#include <unistd.h>int main()
{printf("before fork, pid = %d\n", getpid());pid_t id = fork();assert(id != -1);(void)id;printf("after fork, pid = %d, fork return %d\n", getpid(), id);return 0;
}

在这里插入图片描述
上面代码执行路径如下下图所示↓↓↓
在这里插入图片描述
进程调用fork。将会从用户空间转换到内核空间,内核做了如下内容:
①分配新的内存块和内核数据结构给子进程
②将父进程部分数据结构内容拷贝给子进程
③添加子进程到系统进程列表中
④fork返回,开始调度器调度

当进程调用fork后,就会有二进制代码相同的的两个进程,它们均运行到相同的地方,但两个代码相互独立,各自执行各自的。上面代码中,两个进程虽然执行了同一份代码,但由于父子进程的id值、pid值不同,故打印的结果不同。同时,这里到底是父进程先打印还是子进程先打印,取决于调度器的调度,哪个进程先被调度是不一定的。

★ps:创建子进程时,给子进程分配对应的内核结构,必须是子进程独有的,因为进程具有独立性。理论上,子进程也要有自己的代码和数据,但在子进程刚创建时,它的代码和数据是与父进程共享的。只有当子进程进行程序替换(修改代码)或对数据做修改等操作时,会发生写时拷贝,为子进程创建独立的存储空间。

fork的应用常见有哪些呢?

  • 一个父进程希望复制自己,使父进程同时执行不同的代码段。例如:父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行另一个完全独立的程序,类似于shell。

fork调用失败的原因有哪些?

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

写时拷贝

通常,父进程创建子进程后,在父进程不进行代码及数据的修改时,物理地址上的数据是父子进程共享的。

在这里插入图片描述
但当父进程或子进程尝试对代码或数据进行修改时,就会发生写时拷贝。若子进程尝试修改某个变量,此时发生写时拷贝,会给子进程开辟一个该变量的物理地址,子进程修改的结果保存在该物理地址上。
在这里插入图片描述
操作系统为什么选择写时拷贝呢?
①写时拷贝用的时候再分配空间,是一种延迟申请技术,是提高内存使用效率的表现,可以提高整机的内存使用效率;(不需要子进程复制父进程的全部数据,防止浪费空间)
②操作系统无法在代码执行前预知子进程哪些代码和父进程不同,因而无法预知子进程哪些代码和数据需要与父进程共享,哪些需要独立存储;
③因为写时拷贝的存在,父子进程不同的代码和数据得以分离,保证了进程的独立性。

进程退出

进程退出操作系统做了什么?

操作系统要释放进程申请的相关内核数据结构和对应的数据和代码(本质就是释放系统资源)。

进程退出场景

  • 代码执行完毕,结果正确
#include <stdio.h>
int Add(int from, int to)
{int sum = 0;for(int i = from; i <= to; i++){sum += i;}return sum;
}
int main()
{printf("Add 1 to 100 is %d\n", Add(0, 100));return 0;
}

在这里插入图片描述

  • 代码运行完毕,结果不正确
#include <stdio.h>
int Add(int from, int to)
{int sum = 0;/*应该为i<=to*/for(int i = from; i < to; i++){sum += i;}return sum;
}
int main()
{printf("Add 1 to 100 is %d\n", Add(0, 100));return 0;
}

在这里插入图片描述

  • 代码异常终止。即代码没有跑完,程序崩溃。
#include <stdio.h>int main()
{int* p = NULL;*p = 100;//野指针异常return 0;
}

在这里插入图片描述

  • 在程序执行结束时,我们会使用return语句返回一个数值作为main函数的返回值。这个返回值是做什么用的呢?

【例子1】小明参与了一场考试后,回家后给老爹汇报成绩。
如果小明考了100分(满分100),那么他的老爹并不会关心他为什么考了100分;但当小明考了3分,他的老爹会关心他考3分的原因。如果做出如下约定,每个数字标识不同的原因:

状态码描述
1生病了导致没考好
2没好好学习导致没考好

在操作系统中,对于程序正常终止我们不做关心,但程序一旦出错,我们就需要知道程序出错的原因。在操作系统约定:如果进程返回的状态码为0,则标识进程正常结束;如果进程返回的状态码非0,则标识进程异常结束(不同的非零值标识不同的错误原因)。操作系统对于不同的状态码给了不同的错误描述信息,我们可以使用errno.h下的errno变量获取错误码,使用strerror(errno)获取错误码的错误描述。下面使用程序获取Linux的不同错误码及其描述↓↓↓

#include <stdio.h>
#include <string.h>int main()
{for(int i = 0; i < 200; i++){printf("[%d]->%s\n", i, strerror(i));}return 0;
}

在这里插入图片描述
★ps:我们自己写的程序可以不遵守操作系统提供的错误码与错误描述的对应关系,我们可以自定义错误码与错误码描述。

  • 那谁会关心当前进程的退出码呢?

子进程的退出码会返回给它的上一级进程,一般而言当前进程的父进程要关心当前进程的退出码,用于评判该子进程执行代码是否正确。例如我们登录Linux系统时使用的bash,它通过创建子进程程序来执行可执行文件(命令、用户程序等),它可以显示最近一次执行的子进程的退出码。我们可以使用echo $?来查看↓↓↓
在这里插入图片描述

  • 一旦程序异常终止,而不是正常终止的,此时的退出码还有意义吗?

【例子2】小明考试作弊
小明每门考试时都作弊考了100分。学校在颁发奖学金的时候会将小明计算在内吗?显然不会,因为小明的考试行为出现了异常,它的结果就不再被关心。

同理,如果程序出现了异常,此时的程序还来不及设置退出码,此时的退出码的数值是不确定的,故此时的退出码没有意义。

  • 程序出现异常,无法通过退出码评判程序异常原因,那要使用什么来查看程序的异常原因呢?

答案是信号。如果我们写出一个除0的错误程序,我们会看到什么的结果呢?

#include <stdio.h>int main()
{int a = 8 / 0;return 0;
}

在这里插入图片描述

在该程序发生除0错误时,操作系统给执行该程序的进程发生了8号信号SIGFPE。我们可以使用kill -l查看所有的信号码与对应信号名↓↓↓
在这里插入图片描述
我们来验证一下,上面的程序就是收到8号SIGFPE才终止的。我们执行下面代码对应的可执行程序,并给对应的进程发送8号信号,验证上述进程确实收到了8号信号才异常终止↓↓↓

#include <stdio.h>int main()
{while(1){}return 0;
}

在这里插入图片描述
在这里插入图片描述

进程退出的常见方法

正常终止与异常终止

正常终止

  • 从main函数返回,即return+退出码
  • 调用exit
  • 调用_exit

异常终止
由于程序内部逻辑错误导致出现异常时,操作系统会给当前进程发送信号来终止程序,这种方式就是异常终止。当然,用户也可以通过kill -[信号编号] [进程pid]来给对应进程发送信号,使其异常终止。

★ps:ctrl + C本质是给当前bash中正在执行的前台进程发送2号信号,其他热键如ctrl + Z等本质也是通过给进程发送信号来实现的。

正常终止中的return+退出码及异常终止方式在上文已经有介绍了,下面我们来介绍一下exit、_exit方式如何使用,及它们的区别。

缓冲区的概念

在介绍exit与_exit前,我们要先理解一个概念——缓冲区↓↓↓

#include <stdio.h>int main()
{printf("jammingpro is coding...");while(1){}return 0;
}

在这里插入图片描述
从上面代码可以发现,在printf执行结束后,并没有打印字符串内容。这时因为,当我们使用C语言向显示器打印信息时,打印内容会被放入一片缓冲区中,如果缓冲区满了,或遇到\n时才会刷新缓冲区(C语言是行缓冲的,所以遇到\n会刷新缓冲区),将内容刷新到显示器中。当然,程序正常执行结束后(return 0),缓冲区的内容也会被刷新。

如果我们想让内容马上刷新到显示器上,可以使用stdio.h下的fflush手动刷新缓冲区↓↓↓

#include <stdio.h>int main()
{printf("jammingpro is coding...");fflush(stdout);while(1){}return 0;
}

在这里插入图片描述

exit与_exit的区别

_exit函数
status定义了进程的退出状态,父进程可以通过wait来获取status(关于wait,将在下文介绍)。
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>int main()
{printf("jammingpro");_exit(1);return 0;
}

在这里插入图片描述

从_exit的示例代码可以发现_exit执行并不会刷新缓冲区,故最后并没有打印"jammingpro"字符串。

exit函数
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>int main()
{printf("jammingpro");exit(1);return 0;
}

在这里插入图片描述
从_exit和exit对同一份代码的不同执行结果可以看出,_exit在退出时不会刷新缓冲区,而exit退出时会刷新缓冲区。

exit与_exit相同的是:它们两者均会调用内核的_exit;不同的是exit除了调用_exit,还做了其他工作:
①执行用户通过atexit或on_exit定义的清理函数
②关闭所有打开的流,所有的缓存数据均被写入(即刷新缓冲区)
③再调用_exit
在这里插入图片描述

★ps:main函数中,return语句就是终止进程,即return+退出码;如果return语句在main函数中,则标识进程结束;如果return语句位于非main函数的其他函数中,则表示该函数执行结束,而不是进程结束。而exit可以在代码的任何地方调用,均表示终止当前进程。

进程等待

进程等待的必要性

当一个进程fork出子进程后,父进程没有对子进程的进行回收时,子进程就会变成僵尸状态。此时的子进程无法被任何信号终止(包括kill -9),因为信号是用于终止进程的,而处于僵尸状态的进程已经处于终止状态,只是对应的系统资源尚未被回收,谁也无法杀死一个已经死去的进程。

子进程的僵尸只能由其父进程解决,父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息(因为父进程需要知道自己派给子进程运行的结果是否正确,子进程是否正常退出等)。

★ps:C/C++程序执行完后,空间会被系统回收,则不会发生内存泄漏。但如果类似服务器程序,它一直跑,不会结束,则会发生内存泄漏。因为当前进程未执行结束,则系统资源不会被回收,而由于内存没有释放,导致大量无用的内存空间无法被申请使用,故产生内存泄漏。

进程等待方法

wait方法

当等待子进程成功时,会返回等待成功的子进程的pid,失败则返回-1。而传入的参数status为输出型参数,如果需要回去子进程的退出状态,则需要传入status;若不关心,则可以填写NULL。
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){int cnt = 5;while(cnt){printf("[%d]->I am child process! pid = %d, ppid = %d\n", cnt, getpid(), getppid());cnt--;sleep(1);}exit(0);}int status = 0;pid_t ret = wait(&status);if(ret > 0){printf("wait %d success! status = %d\n", ret, status);}return 0;
}

在这里插入图片描述
从上面代码可以发现,当子进程在执行时,父进程并不会执行wait之后的if判断语句,而是阻塞在pid_t ret = wait(&status);处等待子进程退出。故wait是阻塞等待的。

这里的status表示什么含义呢?其实,status并不是像上面代码那样使用的。下面介绍status如何使用↓↓↓

status是一个输出型参数,它的值在调用wait及接下来将要介绍的waitpid时,由操作系统自动填充。如果传递NULL,表示不关心子进程的退出状态信息;否则,操作系统会通过该参数,将子进程的退出信息反馈给父进程。

status不能像上面的代码那样使用,即不能将status看作是整型,而要将status当作位图来看待。这里我们只研究status的低16位,它的低16位信息如下↓↓↓
①正常终止时,第8到第15位保存进程的退出状态
②异常终止时,第8到第15位保存进程的退出状态(但异常终止的退出状态不一定能正确标识进程的退出状态,这里的退出状态是无效的,直接忽略),第7位为coredump标志位,这里先忽略这一位,将于后面的文章中介绍,第0到7位存储的是当前进程收到的信号。
在这里插入图片描述
如果我们想获取子进程的退出码和退出信号,则上面的代码应该修改成下面这样↓↓↓

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){int cnt = 5;while(cnt){printf("[%d]->I am child process! pid = %d, ppid = %d\n", cnt, getpid(), getppid());cnt--;sleep(1);}exit(0);}int status = 0;pid_t ret = wait(&status);if(ret > 0){printf("wait %d success! exitcode = %d sig = %d\n", ret, ((status >> 8) & 0xFF), (status & 0x7F));}return 0;
}

在这里插入图片描述
如果父进程创建了多个子进程,则wait等待的是哪一个进程呢?wait等待任意一个进程,只要等待到某个进程,则父进程不再阻塞于当前的wait语句上。下面代码演示了创建5个子进程,并用父进程循环回收5个子进程↓↓↓

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{int i = 0;for(; i < 5; i++){pid_t id = fork();if(id == 0){int cnt = 5;while(cnt){printf("[%d]->I am child process! pid = %d, ppid = %d\n", cnt, getpid(), getppid());cnt--;sleep(1);}exit(0);}}i = 0;while(; i < 5; i++){int status = 0;pid_t ret = wait(&status);if(ret > 0){printf("wait %d success! exitcode = %d sig = %d\n", ret, ((status >> 8) & 0xFF), (status & 0x7F));}}return 0;
}

在这里插入图片描述

waitpid方法

  • waitpid与wait相同,如果等待子进程成功,则会返回该子进程的pid,如果失败则返回-1;且一样可以传入status获取子进程的退出状态信息(用法与wait相同)。

waitpid多了两个参数,即pid和options。

  • 当指定pid为某个子进程的pid时,waitpid只会等待该子进程的pid,而不会等待其他子进程;如果将pid指定为-1,则表示等待任意一个子进程。
  • options设置为0时,表示阻塞等待,这与wait相同;如果设置为WNOHANG表示非阻塞式等待。关于阻塞等待和非阻塞等待,将于下文介绍。

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){int cnt = 5;while(cnt){printf("[%d]->child process, pid = %d\n", cnt, getpid());cnt--;sleep(1);}}int status = 0;pid_t ret = waitpid(id, &status, 0);if(ret == id){printf("wait %d success! exitcode = %d, sig =%d\n", ret ,((status >> 8) & 0xFF), (status & 0x7F));}return 0;
}

在这里插入图片描述

下面我们来谈谈阻塞等待与非阻塞等待↓↓↓

【例子3】期末考前,临时抱佛脚的小明[阻塞等待篇]
因为明天要考操作系统,小明想要求助他们班的学霸李华,让他帮忙画重点并辅导一下,作为报酬,小明要请李华吃饭。小明来到李华家楼下,打电话给李华,李华告知他需要等一会儿,他还需要复习一会儿,需要50分钟左右。小明在李华家楼下不做其他事,他每1分钟给李华打电话,询问他是否可以出发去吃饭辅导了,知道李华下楼了,他才停止。

像这种循环等待,而不做其他事情,这种等待就称为阻塞式等待。上面的wait和waitpid的代码示例均是阻塞等待的,父进程在wait和waitpid函数上阻塞等待子进程退出,而不做其他事,等子进程执行结束才执行下面的if判断语句。

阻塞式等待可以及时回收子进程,但父进程花费大量时间阻塞等待子进程,只要CPU时间片轮到父进程,父进程就一直循环等待子进程,这样效率明显不高。

【例子4】考试前,临时抱佛脚的小明[非阻塞等待篇]
因为上次操作系统被李华辅导后,小明考了61分,至少没有挂科。操作系统考完的隔天要考数据结构,小明又找到李华帮他复习。小明来到李华家楼下,李华告诉他需要等待30分钟左右,这次小明每复习数据结构5分钟,才给李华打一次电话。

像这种再等待时,顺带做一下其他任务,这种称为非阻塞等待。非阻塞等待执行的其他任务不应花费过多时间,以免影响父进程对子进程的等待与回收(因为父进程等待子进程才是主要任务,执行其他代码只是顺带执行,不是重点)。

下面给出一个非阻塞式等待的代码↓↓↓

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define NUM 5typedef void(*func_t)();
func_t funcSet[NUM];void task1()
{printf("处理日志信息\n");
}void task2()
{printf("安全检测任务\n");
}void task3()
{printf("其他处理任务\n");
}void loadTask()
{funcSet[0] = task1;funcSet[1] = task2;funcSet[2] = task3;
}int main()
{loadTask();pid_t id = fork();if(id == 0){int cnt = 5;while(cnt){printf("[%d]->child process, pid = %d\n", cnt, getpid());cnt--;sleep(1);}exit(0);}int status = 0;while(1){pid_t ret = waitpid(id, &status, WNOHANG);if(ret == id){printf("wait %d success! exitcode = %d, sig =%d\n", ret ,((status >> 8) & 0xFF), (status & 0x7F));break;}else{int i = 0;for(; i < 3; i++){funcSet[i]();}}usleep(500000);}return 0;
}

在这里插入图片描述
★ps:进程等待时,要保证父进程最后退出。

★ps:waitpid等待错误的时候会返回-1,当等待的子进程pid不是该进程的子进程时,则会返回-1;如果等待进程还未执行结束,则会返回0;等待成功则返回子进程的pid。

★ps:除了使用((status >> 8) & 0xFF),系统还提供了WEXITSTATUS(status)用于获取子进程的退出码;提供了WIFEXITED(status)判断子进程是否等待成功,还提供了其他宏函数,请使用man手册查阅。下面给出上面两个宏函数使用的代码↓↓↓

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){int cnt = 5;while(cnt){printf("[%d]->child process, pid = %d\n", cnt, getpid());cnt--;sleep(1);}exit(0);}int status = 0;pid_t ret = waitpid(id, &status, 0);if(WIFEXITED(status)){printf("wait %d success! exitcode = %d\n", ret ,WEXITSTATUS(status));}return 0;
}

在这里插入图片描述

wait/waitpid获取子进程信息原理

进程具有独立性,子进程的退出码等不也是子进程的数据吗?父进程凭什么拿到呢?wait/waitpid究竟是如何拿到子进程的退出状态的信息的?

当子进程处于僵尸状态时,系统至少要保留子进程的PCB,PCB中保存着子进程的退出码、收到的信号等退出信息。

当父进程在CPU上执行waitpid时,则进入内核态。处于内核态的执行语句有操作系统的权限,因此它可以读取子进程的退出信息,并将该退出信息填写入status。
在这里插入图片描述

🎈欢迎进入从浅学到熟知Linux专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d

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

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

相关文章

linux系统USB/IP远程共享USB设备 —— 筑梦之路

概述 USB/IP 是一个开源项目&#xff0c;已合入 Kernel&#xff0c;在 Linux 环境下可以通过使用 USB/IP 远程共享 USB 设备。 USB Client&#xff1a;使用USB的终端&#xff0c;将server共享的usb设备挂载到本地。 USB Server&#xff1a;分享本地的usb设备至远程。 架构原理…

归并排序详解(附代码)

归并排序 数据科学家每天都在处理算法。 然而&#xff0c;数据科学学科作为一个整体已经发展成为一个不涉及复杂算法实现的角色。 尽管如此&#xff0c;从业者仍然可以从建立对算法的理解和知识库中受益。 在本文中&#xff0c;对排序算法归并排序进行了介绍、解释、评估和实…

vue3从精通到入门4:diff算法的实现

Vue 3 的 diff 算法相较于 Vue 2 有了一些改进和优化&#xff0c;主要是为了应对更复杂的组件结构和更高的性能需求。 以下是 Vue 3 diff 算法在处理列表更新时的大致步骤&#xff1a; 头头比较&#xff1a;首先&#xff0c;比较新旧列表的头节点&#xff08;即第一个节点&…

《Stable Diffusion AI绘画宝典:从入门到精通,解锁创意新境界》

前沿 在数字化浪潮席卷全球的时代&#xff0c;人工智能以其惊人的创造力和创新性引领着科技新风尚。党的二十大报告明确提出了“实施科教兴国战略&#xff0c;强化现代化建设人才支撑”的宏伟蓝图&#xff0c;展现了我国在新动能、新优势方面的坚定决心和强大气魄。在这个大背…

【Linux】进程的优先级环境变量

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 前言2. 进程的优先级2.1 什么是优先级2.2 为什么要有优先级2.3 优先级的查看方式2.4 对优先级调整 3. 命令行参数4. 环境变量4.1 环境变量与配置文件4.1.1 环境变量初步介绍4.1.2 配置文件 4.2 更多环境变量4.3 整…

Postgresql源码(125)游标恢复执行的原理分析

问题 为什么每次fetch游标能从上一次的位置继续&#xff1f;后面用一个简单用例分析原理。 【速查】 恢复扫描需要知道当前页面、上一次扫描到的偏移位置、当前页面一共有几条&#xff1a; 当前页面&#xff1a;HeapScanDesc结构中记录了扫到的页面&#xff08;scan->rs_cb…

Apache Paimon 流式湖仓介绍说明

文章目录 前言选择 Paimon 的原因Apache Paimon 功能一致性保证Paimon 表类型数据湖写入标签和时间线回溯捕获变更数据写入数据湖LSM 和分层文件重用流处理案例使用 Paimon 作为消息队列 前言 Apache Flink 自诞生以来经历了重大演变&#xff0c;如今&#xff0c;它不仅充当批…

毕设选51还是stm32?51太简单?

如果你更倾向于挑战和深入学习&#xff0c;STM32可能是更好的选择。如果你希望更专注于底层硬件原理&#xff0c;51可能更适合。我这里有一套嵌入式入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习嵌入式&#xff0c;不妨点个关注&#xff…

阿里云迁移到AWS云,九河云保姆级教程

随着云计算技术的不断发展,越来越多的企业开始将传统的IT基础设施迁移到云平台上,以获得更高的灵活性、可扩展性和成本效益。在众多云服务提供商中,阿里云和AWS都是备受青睐的选择。本文将探讨如何将阿里云上的资源顺利迁移到AWS云平台,并针对性地进行优化。我们九河云&#xf…

[图解]DDD领域驱动设计伪创新-聚合根06

0 00:00:00,740 --> 00:00:02,200 那刚才讲了 1 00:00:02,480 --> 00:00:04,211 Evans这个隐喻 2 00:00:04,211 --> 00:00:06,520 实际上背后是把集合 3 00:00:06,800 --> 00:00:08,560 当成了聚合 4 00:00:10,580 --> 00:00:14,350 那为什么有这样的一个隐…

OpenHarmony实战开发-如何使用AKI轻松实现跨语言调用。

介绍 针对JS与C/C跨语言访问场景&#xff0c;NAPI使用比较繁琐。而AKI提供了极简语法糖使用方式&#xff0c;一行代码完成JS与C/C的无障碍跨语言互调&#xff0c;使用方便。本示例将介绍使用AKI编写C跨线程调用JS函数场景。通过调用C全局函数&#xff0c;创建子线程来调用JS函…

GIS 数据格式转换

1、在线工具 mapshaper 2、数据上传 3、数据格式转换 导入数据可导出为多种格式&#xff1a;Shapefile、Json、GeoJson、CSV、TopJSON、KML、SVG

APP广告变现项目

APP广告变现项目 很多人觉得不可能&#xff0c;这是肯定存在的&#xff0c;不是现在才有的一个项目&#xff0c;这个项目的原理是怎么样呢&#xff0c;就是通过某些特定的app&#xff0c;然后看完广告就有收益&#xff0c;基本单次的观看单价都是在几毛到1块之间。 养机养好的…

阿里云服务器带宽多少钱?公网带宽收费标准全解析

阿里云服务器的公网带宽计费模式分为“按固定带宽”和“按使用流量”&#xff0c;有什么区别&#xff1f;按固定带宽是指直接购买多少M带宽&#xff0c;比如1M、5M、10M、100M等&#xff0c;阿里云直接分配用户所购买的带宽值&#xff0c;根据带宽大小先付费再使用&#xff1b;…

一套3种风格经典的wordpress免费主题模板

wordpress免费企业主题 https://www.wpniu.com/themes/39.html 免费wordpress企业模板 https://www.wpniu.com/themes/43.html 免费wordpress企业主题 https://www.wpniu.com/themes/44.html

波奇学Linux:ip协议

ip报头是c语言的结构体 报头和有效载荷如何分离&#xff1f; 固定长度四位首部长度 4位版本号就是IPV4 8位服务类型&#xff1a;4位TOS位段和位保留字段 4位TOS分别表示&#xff1a;最小延时&#xff0c;最大吞吐量&#xff0c;最高可靠性&#xff0c;最小成本 给路由器提…

【JAVA基础篇教学】第十六篇:Java连接和操作MySQL数据库

博主打算从0-1讲解下java基础教学&#xff0c;今天教学第十六篇&#xff1a;Java连接和操作MySQL数据库。 我将提供一个简单的示例代码&#xff0c;涵盖数据库连接、查询、插入和更新等操作。 一、下载MySQL驱动包 1.下载地址&#xff1a;MySQL :: Download Connector/J 2.解…

Navicat for MySQL 使用基础与 SQL 语言的DDL

一、目的&#xff1a; Navicat for MySQL 是一套专为 MySQL 设计的高性能数据库管理及开发 工具。它可以用于任何版本 3.21 或以上的 MySQL 数据库服务器&#xff0c;并支持大 部份 MySQL 最新版本的功能&#xff0c;包括触发器、存储过程、函数、事件、视图、 管理用户等。…

VMware配置CentOS 7 并实现ssh连接

Vmware 17下载地址 ***永久许可证&#xff1a;***5Y012-8HL8P-0J8U0-032Q6-93KKF CentOS 7 下载地址 一、配置CentOS 如下 创建新的虚拟机&#xff0c;选择典型&#xff0c;点击下一步 选择上述下载镜像存储位置&#xff0c;选择镜像&#xff0c;点击下一步 3.填写相关信息…

微信小程序wx.getLocation 真机调试不出现隐私弹窗

在小程序的开发过程中&#xff0c;首页中包含要获取用户地理位置的功能&#xff0c;所以在这里的onLoad&#xff08;&#xff09;中调用了wx.getLocation()&#xff0c;模拟调试时一切正常&#xff0c;但到了真机环境中就隐私框就不再弹出&#xff0c;并且出现了报错&#xff0…