【Linux】进程控制深度了解

> 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:熟练掌握Linux下的进程控制

> 毒鸡汤:在等待的日子里,刻苦读书,谦卑做人,养得深根,日后才能枝叶茂盛。

> 望小伙伴们点赞👍收藏✨加关注哟💕💕 

🌟前言

        最早的时候我们学习了进程的状态,进程优先级和进程切换,当时不把进程控制加在里面,这里我们单独把它拉出来讲解,学习完本章对进程的板块算是熟练掌握了,咱们话不多说,直接进入今天的主题:【Linux】进程控制深度了解。

⭐主体

我们从以下学习【Linux】进程控制深度了解🥰🥰。

🌙进程创建

💫初识fork函数

  • fork函数的作用:是从已存在进程中创建一个新进程,新进程为子进程,而原进程为父进程。
  • fork函数的返回值:在子进程中返回 0 ,父进程中返回子进程pid,子进程创建失败返回 1 。
#include <unistd.h>
pid_t id = fork(void);

在进程调用fork函数时,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程。
  • 将父进程部分数据结构内容拷贝至子进程。
  • 添加子进程到系统进程列表当中。
  • fork返回,开始调度器调度。

举个栗子:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>int main(void)
{pid_t pid;printf("Before: pid is %d\n", getpid());if ((pid = fork()) == -1){perror("fork()");exit(1);}printf("After:pid is %d, fork return %d\n", getpid(), pid);sleep(1);return 0;
}

运行结果:

可以看到fork前只输出了一次,fork之后输出了两次。

过程分析:

  • Before是由父进程打印的。
  • 调用fork函数之后打印的两个After,则分别由父进程和子进程两个进程执行。
  • fork之前父进程独立执行,而fork之后父子两个执行流分别执行。

注意: fork之后,父进程和子进程谁先执行完全由调度器决定。

💫fork函数返回值

  • 子进程返回0。
  • 父进程返回的是子进程的pid。

fork函数为什么给子进程返回0,给父进程返回的是子进程的PID?

        一个父进程可以创建多个子进程,而一个子进程只能有一个父进程,因此对于子进程来说父进程是不需要被标识的,而对于父进程来说,子进程是需要标识的,因为父进程创建子进程的目的是让其指向对于的任务,只有直到了各个子进程的PID,才能更有效率的工作。

为什么fork函数有两个返回值?

        父进程调用fork函数后,为了创建子进程,fork函数内部会进行一系列复杂的操作,包括创建子进程PCB,虚拟地址空间,创建子进程对应的页表等等。子进程创建完毕后,操作系统还需要将子进程的进程控制块添加到系统进程列表中。

        在fork函数内部执行return语句之前,子进程就已经创建完毕了,那么之后的return语句不仅父进程需要执行,子进程也同样需要执行,这就是fork函数有两个返回值的原因。

💫写时拷贝

  • 当子进程刚刚被创建时,子进程和父进程的数据和代码是共享的。
  • 此时父子进程的代码和数据通过页表映射到物理内存的同一块空间。
  • 当父进程或子进程需要修改数据时,才将父进程的数据在内存当中拷贝一份,再进行修改。

1、为什么数据要进行写时拷贝?

多进程运行需要独享各种资源,多进程运行期间是互不干扰,不能让子进程的修改影响到父进程。

2、为什么不在创建子进程的时候就进行数据的拷贝?

子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,我们应该按需分配,在需要修改数据的时候再分配(延时分配),这样可以高效的使用内存空间。

3、代码会不会进行写时拷贝?

90%的情况下是不会的,但这并不代表代码不能进行写时拷贝,例如在进行进程替换的时候,则需要进行代码的写时拷贝。

问题1. 地址空间不隔离
所有程序都直接访问物理内存,程序使用的物理空间不是相互隔离的。万一进程越界进行非法操作,这样是非常不安全的。

问题2. 内存使用效率低
没有有效的内存管理机制,通常执行一个程序时,监控程序需要将其整个程序装入内存然后开始执行。

问题3. 程序运行的地址不确定
因为每次需要装入运行时,我们都需要给他分配一块足够大的物理空间,而这个物理空间是不确定的。

💫fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

💫fork调用失败原因

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

🌙进程终止

💫进程退出场景

  • 代码运行完毕,结果正确。
  • 代码运行完毕,结果不正确。
  • 代码异常终止(进程崩溃)。

💫进程退出码

  • 我们都知道main函数是代码的入口,但实际上main函数只是用户级别代码的入口,main函数也是被其他函数调用的,例如在VS2013当中main函数就是被一个名为__tmainCRTStartup的函数所调用,而__tmainCRTStartup函数又是通过加载器被操作系统所调用的,也就是说main函数是间接性被操作系统所调用的。
  • 既然main函数是间接性被操作系统所调用的,那么当main函数调用结束后就应该给操作系统返回相应的退出信息,而这个所谓的退出信息就是以退出码的形式作为main函数的返回值返回,我们一般以0表示代码成功执行完毕,以非0表示代码执行过程中出现错误,这就是为什么我们都在main函数的最后返回0的原因。
  • 当我们的代码运行起来就变成了进程,当进程结束后main函数的返回值实际上就是该进程的进程退出码,我们可以使用echo $?命令查看最近一次进程退出的退出码信息。

举个栗子1:

#include <stdio.h>int main()
{printf("hello linux\n");return 0;
}

使用指令查看进程退出码1:

echo $?

过程分析1:

为什么以0表示代码执行成功,以非0表示代码执行错误?

因为代码执行成功只有一种情况,成功了就是成功了,而代码执行错误却有多种原因,例如内存空间不足、非法访问以及栈溢出等等,我们就可以用这些非0的数字分别表示代码执行错误的原因。

C语言当中的strerror函数可以通过错误码,获取该错误码在C语言当中对应的错误信息:

举个栗子2:

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

运行结果2:

过程分析2:

        实际上Linux中的ls、pwd等命令都是可执行程序,使用这些命令后我们也可以查看其对应的退出码。可以看到,这些命令成功执行后,其退出码也是0。

💫进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):

  • 从main返回,return退出
  • 调用exit
  • _exit

异常退出:

  • ctrl+c,信号终止
1.return

return是一种常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数,会将main的返回值当做 exit的参数。

2.exit

使用exit函数退出进程也是我们常用的方法,exit函数可以在代码中的任何地方退出进程,并且exit函数在退出进程前会做一系列工作:

  1. 执行用户通过atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入。
  3. 调用_exit函数终止进程。

举个栗子:

#include <stdio.h>
#include <stdlib.h>void show()
{printf("hello linux\n");exit(1);
}int main()
{show();return 0;
}

运行结果:

结果分析:

exit终止进程前会将缓冲区当中的数据输出。

3._exit函数

使用_exit函数退出进程的方法我们并不经常使用,_exit函数也可以在代码中的任何地方退出进程,但是_exit函数会直接终止进程,并不会在退出进程前会做任何收尾工作。

举个栗子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>void show()
{printf("hello linux");_exit(2);
}int main()
{show();return 0;
}

运行结果:

结果分析:

使用_exit终止进程,则缓冲区当中的数据将不会被输出。

💫进程异常退出

情况一:向进程发生信号导致进程异常退出。

例如,在进程运行过程中向进程发生kill -9信号使得进程异常退出,或是使用Ctrl+C使得进程异常退出等。

情况二:代码错误导致进程运行时异常退出。

例如,代码当中存在野指针问题使得进程运行时异常退出,或是出现除0的情况使得进程运行时异常退出等。

🌙进程等待

💫进程等待的必要性

  • 子进程退出,父进程如果不读取子进程的退出信息,子进程就会变成僵尸进程,进而造成内存泄漏。
  • 进程一旦变成僵尸进程,那么就算是kill -9命令也无法将其杀死,因为谁也无法杀死一个已经死去的进程。
  • 对于一个进程来说,最关心自己的就是其父进程,因为父进程需要知道自己派给子进程的任务完成的如何。
  • 父进程需要通过进程等待的方式,回收子进程资源,获取子进程的退出信息。

💫获取子进程状态status

        下面会介绍有关进程等待的两个函数wait和waitpid,它们都有一个status参数,改参数是一个输出型参数,由操作系统进行填充。如果对该参数传入NULL,表示不关心子进程的退出状态信息。反之,操作系统会通过该参数,将子进程的退出状态信息反馈给父进程。

如何理解参数status?

        status实际上是一个整型变量,但是status不能当作整型里看待,因为status的不同的比特位所代表的信息不同(只研究低16位比特位)。在status的低16位比特位中,高8位表示进程的退出状态,即退出码。进程若是被信号所杀,低7位表示终止信号,而第8位是core dump标志。需要注意的是,当一个进程非正常退出时,说明该进程是被信号所杀,那么该进程的退出码也就没有意义了。

我们通过一系列位操作,就可以根据status得到进程的退出码和退出信号。

exitCode = (status >> 8) & 0xFF; //退出码
exitSignal = status & 0x7F;      //退出信号

对于此,系统当中提供了两个宏来获取退出码和退出信号。

  • WIFEXITED(status):用于查看进程是否是正常退出,本质是检查是否收到信号。
  • WEXITSTATUS(status):用于获取进程的退出码。
exitNormal = WIFEXITED(status);  //是否正常退出
exitCode = WEXITSTATUS(status);  //获取退出码

需要注意的是,当一个进程非正常退出时,说明该进程是被信号所杀,那么该进程的退出码也就没有意义了。

💫进程等待的方法

1.wait方法
  • 函数原型:pid_t wait(int* status);
  • 作用:等待任意子进程。
  • 返回值:等待成功返回被等待进程的pid,等待失败返回-1。
  • 参数:输出型参数,获取子进程的退出状态,不关心可设置为NULL。

举个栗子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id = fork();if (id == 0) {	int count = 10;while (count--) {printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid());sleep(1);}exit(0);}int status = 0;pid_t ret = wait(&status);if (ret > 0) {	printf("wait child success...\n");if (WIFEXITED(status)) {		printf("exit code:%d\n", WEXITSTATUS(status));}}sleep(3);return 0;
}

利用指令对进程进行实时监控:

while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep;echo "######################";sleep 1;done

运行结果:

结果分析:

  • 创建子进程后,父进程可使用wait函数一直等待子进程,直到子进程退出后读取子进程的退出信息。
  • 这时我们可以看到,当子进程退出后,父进程读取了子进程的退出信息,子进程也就不会变成僵尸进程了。
1.waitpid方法
  • 函数原型:pid_t waitpid(pid_t pid, int* status, int options);
  • 作用:等待指定子进程或任意子进程。
  • 返回值:
  1. 等待成功返回被等待进程的pid。
  2. 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0。
  3. 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在。
  • 参数:
  1. 等待成功返回被等待进程的pid。
  2. 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0。
  3. 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在。

举个栗子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id = fork();if (id == 0){//child          int count = 10;while (count--){printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid());sleep(1);}exit(0);}//father           int status = 0;pid_t ret = waitpid(id, &status, 0);if (ret >= 0){//wait success                    printf("wait child success...\n");if (WIFEXITED(status)){//exit normal                                 printf("exit code:%d\n", WEXITSTATUS(status));}else{//signal killed                              printf("killed by siganl %d\n", status & 0x7F);}}sleep(3);return 0;
}

运行结果:

结果分析:

  • 创建子进程后,父进程可使用waitpid函数一直等待子进程(此时将waitpid的第三个参数设置为0),直到子进程退出后读取子进程的退出信息。
  • 被信号杀死而退出的进程,其退出码将没有意义。

🌙进程程序替换

💫替换原理

        用fork 创建子进程后执行的是和父进程相同的程序 ( 但有可能执行不同的代码分支 ), 子进程往往要调用一种 exec 函数 以执行另一个程序。当进程调用一种exec 函数时 , 该进程的用户空间代码和数据完全被新程序替换 , 从新程序的启动 例程开始执行。调用exec 并不创建新进程 , 所以调用 exec 前后该进程的 id 并未改变。

1.创建子进程的目的?

执行父进程磁盘代码的一部分;
让子进程加载磁盘上指定的程序到内存中,执行新程序的的代码和数据。

2.程序替换的本质

将指定程序的代码和数据加载到指定的位置

3.进程替换的时候,有没有创建新的进程?

        我们知道一个进程被创建出来,OS会给它分配进程PCB,mm_struct,页表等信息,同时会将程序的代码和数据加载到物理内存。要知道进程程序替换之后,该进程的PCB,进程地址空间,页表等信息都不会发生改变,仅仅是把一个新的程序的数据和代码替换了原来进程的代码和数据,只是物理内存当中的数据和代码发生了改变,所以并没有创建新的进程,而且进程程序替换前后该进程的pid也没有改变。

4.子进程进行进程程序替换后,会影响父进程的代码和数据吗?

        子进程刚被创建时,与父进程共享代码和数据,但当子进程需要进行进程程序替换(调用exec函数)时,也就意味着子进程需要对其数据和代码进行写入操作,这时便需要将父子进程共享的代码和数据进行写时拷贝,此后父子进程的代码和数据分离,因此子进程进行进程程序替换后不会影响父进程的代码和数据。

💫替换函数

一、int execl(const char *path, const char *arg, ...);

第一个参数是要执行程序的路径,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。

举例:

execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);

二、int execlp(const char *file, const char *arg, ...);

第一个参数是要执行程序的名字,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。

举例:

execlp("ls", "ls", "-a", "-i", "-l", NULL);

三、int execle(const char *path, const char *arg, ..., char *const envp[]);

第一个参数是要执行程序的路径,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾,第三个参数是你自己设置的环境变量。

举例:

char* myenvp[] = { "MYVAL=2021", NULL };
execle("./mycmd", "mycmd", NULL, myenvp);

四、int execv(const char *path, char *const argv[]);

第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };

execv("/usr/bin/ls", myargv);

五、int execvp(const char *file, char *const argv[]);

第一个参数是要执行程序的名字,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。

举例:

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execvp("ls", myargv);

六、int execve(const char *path, char *const argv[], char *const envp[]);

第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾,第三个参数是你自己设置的环境变量。

举例:

char* myargv[] = { "mycmd", NULL };

char* myenvp[] = { "MYVAL=2021", NULL };

execve("./mycmd", myargv, myenvp);

💫函数解释

  • 这些函数如果调用成功,则加载指定的程序并从启动代码开始执行,不再返回。
  • 如果调用出错,则返回-1。
  • 所以 exec 函数只有出错的返回值而没有成功的返回值。

💫命名理解

  • l(list):表示参数采用列表的形式,一一列出。
  • v(vector):表示参数采用数组的形式。
  • p(path):表示能自动搜索环境变量PATH,进行程序查找。
  • e(env):表示可以传入自己设置的环境变量。

事实上,只有execve才是真正的系统调用,其它五个函数最终都是调用的execve,所以execve在man手册的第2节,而其它五个函数在man手册的第3节,也就是说其他五个函数实际上是对系统调用execve进行了封装,以满足不同用户的不同调用场景的。

下图为exec系列函数族之间的关系:

   🌟结束语

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

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

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

相关文章

【Leetcode】466. 统计重复个数

文章目录 题目思路代码 题目 466. 统计重复个数 思路 题目要求找出一个最大整数 m&#xff0c;使得经过 n2 个字符串 s2 组成的字符串能够被经过 n1 个字符串 s1 组成的字符串完全包含的次数。使用动态规划来记录每个位置匹配的情况&#xff0c;并通过循环节的分析来计算最…

JavaBean

学习目的与要求 熟练掌握<jsp:useBean>、<jsp:setProperty>、<jsp:getProperty>等JSP的操作指令。 本章主要内容 编写JavaBean在JSP中使用JavaBean 一个JSP页面通过使用HTML标记为用户显示数据&#xff08;静态部分&#xff09;&#xff0c;页面中变量的…

利用码云(Gitee)与IDEA轻松管理远程代码库的完整指南

目录 前言1 码云简介2 码云上创建远程库3 IDEA集成码云的步骤3.1 安装Gitee插件并建立连接3.2 项目分享到码云3.3 拉取代码 4 码云复制Github4.1 迁移github项目到码云4.2 代码同步 结语 前言 在软件开发领域&#xff0c;代码托管平台是开发者不可或缺的利器。Github作为全球最…

Qt实现文本编辑器(二)

上一章节讲述了如何制作文本编辑页面&#xff0c;以及应该有哪些功能需要实现&#xff0c;只是做了展示效果&#xff0c;实际的点击事件并没有处理。今天来具体讲解下是如何实现菜单栏以及工具栏上对应的需求吧~ 功能实现 功能&#xff1a; 1、动作消息触发 2、具体功能&am…

常见安全概念澄清,Java小白入门(八)

认证 认证 (Identification) 是验证当前用户的身份。 常见的认证技术&#xff1a; 身份证用户名和密码用户手机&#xff1a;手机短信、手机二维码扫描、手势密码用户的电子邮箱用户的生物学特征&#xff1a;指纹、语音、眼睛虹膜 授权 授权 (Authorization) 指赋予用户系统…

Pointnet++改进:更换不同的激活函数,打造更优性能

简介&#xff1a; 1.该教程提供大量的首发改进的方式&#xff0c;降低上手难度&#xff0c;多种结构改进&#xff0c;助力寻找创新点&#xff01; 2.本篇文章对Pointnet进行激活函数的改进&#xff0c;助力解决RELU激活函数缺陷。 3.专栏持续更新&#xff0c;紧随最新的研究内容…

前端Web系统架构设计

文章目录 1.目录结构定义2. 路由封装2.1 API路由定义2.2 组件路由定义 3. Axios请求开发4. 环境变量封装5. storage模块封装(sessionStorage, localStorage)6. 公共函数封装(日期,金额,权限..)7. 通用交互定义(删除二次确认,类别,面包屑...)8. 接口全貌概览 1.目录结构定义 2. …

LeetCode刷题--- 三步问题

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 ​​​​​​http://t.csdnimg.cn/6AbpV 数据结构与算法 ​​​http://t.csdnimg.cn/hKh2l 前言&#xff1a;这个专栏主要讲述动…

【Matlab】PSO-BP 基于粒子群算法优化BP神经网络的数据时序预测(附代码)

资源下载&#xff1a; https://download.csdn.net/download/vvoennvv/88689096 一&#xff0c;概述 PSO-BP算法是一种结合了粒子群算法&#xff08;PSO&#xff09;和BP神经网络的方法&#xff0c;用于数据时序预测。下面是PSO-BP算法的原理和过程&#xff1a; 1. 数据准备&…

继承和多态

全局变量&#xff0c;int monster 10000:定义英雄类hero&#xff0c;受保护的属性string name&#xff0c;int hp,int attck;公有的无参构造&#xff0c;有参构造&#xff0c;虚成员函数 void Ak(blood-0)&#xff0c;法师类继承自英雄类&#xff0c;私有属性 int p_atk50;重写…

Github 2024-01-03 开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2024-01-03统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目3TypeScript项目3Jupyter Notebook项目1Dart项目1C项目1Rust项目1 系统设计指南 创建周期&#x…

计算机毕业设计 SpringBoot的停车场管理系统 Javaweb项目 Java实战项目 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

堆排序 Heapsort

堆排序&#xff08;Heapsort&#xff09;是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构&#xff0c;并同时满足堆积的性质&#xff1a;即子结点的键值或索引总是小于&#xff08;或者大于&#xff09;它的父节点。堆排序可以说是一种利用堆的概…

简易机器学习笔记(四)初识卷积神经网络

前言 第一次写卷积神经网络&#xff0c;也是照着paddlepaddle的官方文档抄&#xff0c;这里简单讲解一下心得。 首先我们要知道之前写的那些东西都是什么&#xff0c;之前写的我们称之为简单神经网络&#xff0c;也就是简单一层连接输出和输出&#xff0c;通过前向计算和逆向…

Simple Facebook Sign-In

简单的Facebook登录为Android、iOS、Windows、Mac、通用Windows平台(UWP)和Unity制作的WebGL应用程序提供了基于OAuth 2.0的Facebook登录。 优点: ● 跨平台游戏和应用程序的跨平台用户身份验证 ● 无插件,无第三方库,无依赖● 对建筑规模没有影响 ● 客户端-服务器应…

solidity显示以太坊美元价格

看过以太坊白皮书的都知道&#xff0c;以太坊比较比特币而言所提升的地方中&#xff0c;我认为最重要的一点就是能够访问外部的数据&#xff0c;这一点在赌博、金融领域应用会很广泛&#xff0c;但是区块链是一个确定的系统&#xff0c;包括里面的所有数值包括交易ID等都是确定…

OS 7--DNS配置+Apache发布网站

环境准备 centOS 7 1.配置DNS 1.1 域名为lianxi.com 1.2 为WWW服务器、FTP服务器、NEWS服务器做域名解析 1)安装DNS yum -y install bind bind-utils (如果安装不上&#xff0c;就把磁盘在重洗挂载一下&#xff09; 2&#xff09;修改DNS配置文件 vim /etc/resolv.conf…

车载 Android之 核心服务 - CarPropertyService 解析

重要类的源码文件名及位置&#xff1a; CarPropertyManager.java packages/services/Car/car-lib/src/android/car/hardware/property/ CarPropertyService.java packages/services/Car/service/src/com/android/car/ 类的介绍&#xff1a; CarPropertyManager&#xff1a…

航芯ACM32G103开发板评测 02-GPIO输入输出

航芯ACM32G103开发板评测 02-GPIO输入输出 航芯ACM32G103开发板评测 GPIO输入输出应用 软硬件平台 ACM32G103 Board开发板 MDK-ARM Keil GPIO输出典型应用——点灯 GPIO输入典型应用——按键 GPIO 功能概述 GPIO 是通用输入/输出&#xff08;General Purpose I/O&#x…

[Flutter]WindowsOS中相关配置

Flutter项目在Windows平台上如何配置 目录 Flutter项目在Windows平台上如何配置 写在开头 正文 1、OS准备 2、编译环境准备 ① 下载AndroidStudio ② 下载dart ③ 下载flutter ④ 下载并安装VS ⑤ 在AS中配置dart和flutter 3、配置中遇到的问题 写在结尾 写在开头…