Linux | 进程理解,fork | 进程地址空间

news2024/11/19 1:50:58

文章目录

    • 冯诺依曼体系结构的理解
      • 为什么要有内存的存在?
    • 操作系统的管理
    • 进程的理解
    • 系统调用接口
    • 进程的查看
    • fork
    • 进程状态
      • Linux进程具体的状态
      • 孤儿进程
      • 总结
    • 进程优先级
      • 怎样修改优先级?
      • 进程其他概念
        • 进程抢占
    • 进程地址空间
      • 利用代码验证地址区域
      • 验证堆区和栈区的增长方向
      • mm_struct
      • 怎么理解一块数据区的属性为只读
      • 程序是怎么变成进程的(进程是如何创建的)?
      • fork为什么能返回两个值?
      • 虚拟地址空间的意义

冯诺依曼体系结构的理解

在这里插入图片描述

大部分计算机都遵守冯诺依曼体系,该体系将输入设备(键盘,鼠标,写板)和输出设备(显示器,打印机)统称为外设,运算器和控制器组成了cpu中央处理器,存储器则是计算机的内存。

cpu与外设的交互较少,大多数时间与存储器进行交互,所以存储器,也就是内存,在体系结构中的地位十分重要。

为什么要有内存的存在?

从技术角度:输入设备和输出设备的运行速度远远低于cpu,如果没有内存,输入设备就只能直接向cpu写入信息,但写入的速度很慢,cpu处理数据的速度却很快,cpu处理完数据向输出设备输出,输出设备接收数据后将其输出的速度也是很慢的,这就造成了cpu有大段时间闲置,输入设备向cpu写了8h的数据,但cpu只1h就将数据处理完了,cpu在剩下的7h无事可做,这样就造成了时间的浪费。

而内存作为cpu和IO设备之间的缓冲设备,内存的速度快于IO设备,慢于cpu,因此,内存就像是一块很大的缓存,它的存在是为了适配外设和cpu速度不均的问题。并且,内存可以预装载数据,当cpu需要时直接将数据给cpu(预装载,局部性原理:当你打开一份文件时,操作系统大概率的认为你要读取该文件下的数据,因此会将这个文件的所有数据预装载到内存中,当你访问文件下的数据时,因为这些数据早就存储在了内存中,现在只需要内存将数据写入cpu,这样一来整个体系结构的速度就被提高了)。

从成本的角度:cpu有存储数据的寄存器,外设也可以将数据写入到寄存器中,但寄存器的价格昂贵,能存储的数据量小,虽然内存的速度低于寄存器,但内存的价格便宜,可以以较低的成本使用大容量的内存,内存的出现很好的解决了价格这一痛点,使得计算机更好的普及。

所以冯诺依曼体系可能不是速度最快的,但却是最适合大部分人,最容易普及的。

之前总听到一句话:所有程序要运行必须先加载到内存中,为什么?因为冯诺依曼体系结构决定,一个程序被保存在磁盘中,在这种情况下,磁盘作为输入设备不能和cpu进行交互,所以磁盘要先将该程序加载到内存中,通过内存间接的与cpu交互。

操作系统的管理

硬件是死的,硬件需要被正确的使用才能完成功能,在计算机中,硬件被操作系统使用

换言之,操作系统作为一款软件,是用来管理这些硬件的。这里需要解释一下管理:管理的本质是对数据的管理,比如校长对学生的管理,校长不对学生进行直接的管理,而是通过数据(考试成绩,绩点排名…)进行管理,对于需要调整的数据,将调整的任务下派给辅导员或者其他老师,借由他们的手对学生进行管理。操作系统也是如此,操作系统不与硬件直接交互,而是对这些硬件的数据进行管理,对于需要调整的数据就借由相应的驱动进行管理。

操作系统管理数据的方式就是:先描述对象的属性,再组织这些属性,一个程序运行起来后,操作系统根据程序的属性进行描述,假设这个程序被描述成一个节点,每个程序都被这样描述后,用链表或其他高效的数据结构将它们组织起来,对这些程序的管理就变成了对该数据结构的管理(增删查改)。

进程的理解

一个程序运行起来后就叫做进程,这句话似乎难以理解,更准确的说,程序运行后,被操作系统描述成一个数据结构,并且由于体系结构决定,程序的相关代码需要被加载到内存中,我们常说的进程就等于对应的数据结构 + 内存中的相关代码。

在Linux中,用task_struct描述一个程序运行后对应的数据结构(Linux底层是用c写的,c语言用struct描述一个复杂对象),在windows中则是用PCB (process ctrl block)描述程序运行后对应的数据结构。

系统调用接口

为了防止恶意或非恶意的对代码的破坏,操作系统不对外暴露自己的代码逻辑与数据结构,只对外提供接口,而Linux系统是由c语言写的,这些接口本质上是c语言函数,所以学习系统编程,其实是对系统调用接口的学习。

站在系统的角度上,平常编写的c/c++代码也调用了系统接口,比如printf,cout,代码在编译的最后与库进行链接,库里的函数实际上调用了系统的接口(因为你需要使用显示器这个硬件设备,需要经过操作系统)。除此之外还有Linux中的指令,shell外壳,也是对系统接口的调用。

当然,不同操作系统对外提供的接口是不同的,所以代码想要具有良好的跨平台性,就需要该语言兼容了多个操作系统,即同一个语言接口可以调用不同操作系统的接口。

进程的查看

在这里插入图片描述
这段代码将死循环执行一条打印语句,那么编译过后的代码形成了一个exe文件,该exe文件运行起来后,成为了一个进程,复制当前会话,在另一个窗口查看exe进程
在这里插入图片描述
用ps axj查看当前进程,ps(process status,进程状态)。在查询结果中使管道,用grep 'a.out’过滤出包含a.out的进程,grep -v grep,过滤掉含有grep的进程。

在这里插入图片描述
在这里插入图片描述
ps axj | head -1,输出ps axj文件的第一行,&&的意思是前面的指令执行完执行后面的指令

根目录下的proc里存储了当前进程的详细信息。
在这里插入图片描述
在这里插入图片描述
通过进程的pid查看进程的详细信息,其中cwd指的是当前工作路径,也就是所谓“当前路径”,如果程序默认生成了文件,将放到这个路径下。
在这里插入图片描述
该进程每次被分配的pid都不同,但父进程的pid都是31686,这是因为在命令行上运行的所有程序都是bash进程的子进程。
在这里插入图片描述
在这里插入图片描述

fork

使用fork函数创建子进程
在这里插入图片描述
fork通过复制当前进程,创建子进程
在这里插入图片描述
运行上面的代码,同一条printf语句却打印出了两行不同的内容,为何?
在这里插入图片描述
同样的,一段代码即走了if还走了else

fork后的代码为什么会被执行两次?

fork函数为当前进程创建一个子进程,子进程与父进程共享代码,所以两个进程都会执行fork之后的代码,这就是printf为什么被打印两次。调用fork后,父子进程的返回值不同,父进程的fork返回子进程的pid,子进程的fork返回0,可以根据不同的返回值进行if,else的判断,使父子进程执行不同的代码块。

fork函数是怎样返回两个值的?

上面说了fork后的代码被执行了两次,fork本质是一个函数,函数的最后一定有个return语句,当函数要执行return语句时,说明函数的功能已经完成,准备返回了。而fork函数的主要功能是:1.创建子进程,生成对应的task_struct。2.将task_struct放入cpu的运行队列中。 父进程执行fork函数时,在fork返回之前已经生成了子进程,由于父子进程共享代码,所以fork函数的return语句一定会被子进程执行,fork函数的return被两个进程分别执行了一次。

fork为什么给子进程返回0,父进程返回非0?

子进程的父进程唯一,所以子进程不用特别标识出父进程,返回0是用来标识子进程创建成功的,但父进程的子进程不唯一,一个父进程可能有n个子进程,给父进程返回子进程的pid是为了让父进程知道自己有这个子进程

进程状态

在这里插入图片描述

运行态:
系统为需要运行的进程创建了一个运行队列,运行态不是指进程被cpu运行,而是指进程被放到运行队列中,表示该进程随时准备被运行
终止态:
终止态也不是指进程被释放,而是进程执行完,可能还在运行队列中,只是永远不被运行了,随时可能被释放

进程被执行完成,为什么不立即释放,而要维护一个终止态?

操作系统可能忙于其他进程的执行,释放进程需要消耗资源与时间,所以等待其他更重要的进程执行完,再来释放。

阻塞态:
当一个进程将要被执行时,对应的task_struct被放到runqueue运行队列中。但进程除了需要访问cpu,可能还要访问一些外设,如显卡,声卡,磁盘,这些外设的速度很慢,如果要访问的外设被其他进程占用,该进程就需要等待外设资源,此时进程的task_struct从cpu的runqueue被放到对应外设的waitqueue上,进程被放到外设的等待队列后的状态被称为阻塞态
挂起态:
当内存不足时,runqueue不能再添加task_struct,这时操作系统会进行辗转腾挪的操作,将短时间内不会被执行的进程置换到磁盘中的swap分区,这时的进程状态叫做挂起态,当内存空闲时再置换回来。所以当计算机内存不足时,往往伴随着磁盘的高频访问。

Linux进程具体的状态

R,S,D

// Linux中的7种进程状态
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 */
};

在这里插入图片描述
这段代码死循环的执行打印,通过查询,代码的进程状态为S+状态在这里插入图片描述
S状态为Linux中的浅休眠状态,即可以被唤醒也可以被杀死,S对应着阻塞态。由于sleep的缘故,该进程在显示器的等待队列中阻塞,直到sleep完成再运行,如果将代码中的sleep去掉
在这里插入图片描述
在这里插入图片描述
查询该进程状态,结果还是S状态,即阻塞态,原因 是cpu执行该进程的速度远大于显示器的打印速度,以至于该进程依然在等待显示器,等待其完成前面的打印。

去掉代码中的打印,进程会一直保持运行态,对应Linux中的R状态
在这里插入图片描述
如果进程等待的资源是磁盘,那么该进程就会处于D状态,D状态也是一种阻塞状态。

为什么有了S状态还要有D状态?
一个进程向磁盘读写数据,需要磁盘的反馈,即告知是否操作成功,若不告知可能导致数据丢失:当磁盘执行进程的指令时,若进程处于S状态,阻塞在等待队列中,在内存压力过大时,操作系统会终止用户进程,终止的进程可能就是这个在等待磁盘读写完成的进程(除了操作系统,该进程还有可能被手动杀死),而磁盘写入完成后,发现该进程被终止,用户也就无法知道是否读取成功。所以D状态被设计出来,用来表示深度睡眠,操作系统无法杀死或唤醒,直到磁盘读写完成,该进程自动唤醒。

Z,X

X状态:进程执行完成,随时准备被释放,即终止态
Z状态:是Linux中独有的状态。当Linux的一个进程退出时,该进程不会直接进入X状态,而是进入僵尸态Z,为何?操作系统中每一个进程都有一个父进程,子进程的完成情况是怎样的,需要告知父进程,根据情况操作系统再进行相关的调整。所以一个进程执行完,要进入Z状态,维护该进程的信息,让父进程读取

可以模拟出一个僵尸进程,使用fork函数,用if判断将子进程退出,父进程什么都不做,这时子进程的状态就是Z
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果一直没人回收僵尸进程,那么该进程会一直占用系统资源,从而导致内容泄漏,所以在编程时,需要特别注意子进程的情况,防止出现僵尸状态。

(状态后跟+,表示这是一个前台进程,可以用ctrl+c停止,后台进程只能用kill -9 xxx杀死,xxx是pid)

孤儿进程

如果子进程的父进程退出了,那么这个子进程就成了孤儿进程,孤儿进程将由操作系统也就是1号进程领养
在这里插入图片描述
在这里插入图片描述
孤儿进程不能用ctrl+c终止,只能用kill指令,kill -9 pid杀死进程

T,t

两个t都是进程的暂停状态,t是进程被调试时的状态。

总结

操作系统的抽象状态概念与linux的具体状态对比

运行态 – R
终止态 – X
挂起态 – S,D,T,t
阻塞态 – S,D

进程优先级

进程优先级是什么? 与权限对比,权限是指一个进程能不能访问某个资源,而优先级是指进程能访问某个资源,只是访问的先后顺序不同。为什么要有优先级? 计算机资源是有限的,而进程总是有很多的,系统对于资源的分配不能面面俱到,需要给重要的进程更多资源,相对不重要的进程更少资源

怎样修改优先级?

在这里插入图片描述
ps -al指令可以查看进程的详细信息,PRI是优先级priority的缩写,NI是nice的缩写,进程的优先级PRI = 80 + NI,NI的取值范围在[-20,19]。

1.敲sudo top
2.敲r
3.输入要设置优先级的进程pid
4.设置nice值,回车

在这里插入图片描述

进程其他概念

竞争性:cpu资源有限,但进程数量很多,每个进程为完成任务,需要竞争cpu资源,于是有了优先级来确定进程使用资源的先后顺序
独立性:每个进程相互独立,一个进程挂掉不会影响另一个进程,多个进程运行期间互不干扰
并发:多个进程在一个cpu下采用进程切换的方式,一段时间内,每个进程都被执行过,称为并发
并行:多个进程在多个cpu下运行,称为并行

当一台电脑有两个cpu,同一时间内可能有两个进程同时在跑,但单cpu的电脑,打开任务管理器,可以看到有多个进程在跑,这是为什么?明明cpu在同一时间内只能运行一个进程。

一个进程占用cpu资源,该进程不可能一直占用cpu资源直到运行完成,操作系统有一个时间片的概念,每一个进程的调度周期被称为时间片,调度周期结束后,cpu就去调用其他进程,所以在一段时间内多个进程都会被执行,由于时间片很短,给我们的感觉就是多个进程同时在跑。

进程抢占

如果现在有一个优先级较高的进程需要被执行,cpu在执行的进程的优先级小于该进程,这时cpu会终止正在执行的进程,执行优先级较高的进程,这样的现象叫做进程抢占。

前面说cpu执行进程的方式是切换式执行的,但cpu执行一个进程必定产生大量的临时数据保存在cpu的寄存器中(这些临时数据被称作上下文数据),当上一个进程被剥离时,这些数据要去哪?首先cpu寄存器的资源很少,前一个进程的上下文数据不可能保存在寄存器中,所以数据会被保存到进程控制块PCB中,也就是内存中,下次执行该进程时再读取恢复这些数据。

进程地址空间

一个程序跑起来后,需要一块空间存储数据,这块空间叫做程序地址空间,从下到上分别为代码区,初始化数据区,未初始化数据区,堆区,栈区,命令行参数环境变量区。
在这里插入图片描述

利用代码验证地址区域

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

验证堆区和栈区的增长方向

在这里插入图片描述
在这里插入图片描述
通过打印的地址我们可以看到堆区向高地址增长,栈区向低地址增长。所以在栈区先定义的变量地址高,在堆区先定义的变量地址低。

并且堆区和栈区有非常大的地址镂空,这也侧面说明了堆区向上增长,栈区向下增长,中间的镂空是一块存储空间。

mm_struct

在这里插入图片描述
在这里插入图片描述
用fork创建子进程,创建子进程之前已经创建了全局变量g_val,循环输出两个进程的pid,g_val的值以及地址。在5秒后将子进程的g_val值修改,通过打印结果可以看到,两个进程的g_val地址都是相同的,但是在子进程修改g_val的值后,父进程g_val的值却没有改变,而且两个进程g_val的地址却是相同的。

可以证明,这两个进程没有使用同一块内存空间,而是两块分别独立的存储空间,如果两个进程使用相同的内存空间,怎么解释相同的地址,子进程读取时是400,父进程读取时是100?所以每一个进程都有一块自己的进程地址空间

之前说过一个可执行程序被加载到内存后创建了task_struct,形成了进程,task_struct中有一个结构——mm_struct,虚拟地址空间被存储到结构体mm_struct中,虚拟地址空间就是刚刚说的栈区,堆区,代码区那块空间。mm_struct存储了将虚拟地址映射到真实地址的页表,进程可以通过页表访问真实的物理空间。

怎么理解一块数据区的属性为只读

存储器是一种硬件,硬件没有能力规定你是否能访问它,所以你对硬件的读或写操作都是允许的,但如果一块硬件是只读的,那么硬件中已经存在的数据是怎么被写入的? 实际的情况是:操作系统在你和硬件中加了一层软件层,访问硬件必须经过这层软件层,只读数据区由这层软件层管理,当你想往只读数据区写入数据时,被这层软件层拦截,你就无法访问只读数据区。

这层软件层可以理解为进程地址空间,进程地址空间也叫作虚拟地址空间,虚拟地址空间中的只读数据区对应着物理内存中的一块区域,当你想写入只读数据区时,页表中可能没有该虚拟地址与物理地址的映射,或者页表中没有这个地址,访问该地址是非法的,系统通过页表的映射关系拦截写入只读数据区的操作,同理,野指针越界的检查也是通过这种方式

再看最开始的问题,父进程创建子进程时,子进程与父进程共享代码与数据,所以两个进程的g_val的地址是相同的,但这个相同指的是虚拟地址相同,两个进程相同的虚拟地址实际指向物理内存的不同空间,所以修改了子进程的g_val值,只是通过页表找到虚拟地址映射的物理地址,将该物理地址上的数据修改,而父进程的g_val的物理地址上的数据并没有修改。

程序是怎么变成进程的(进程是如何创建的)?

首先明白两点:1.代码被编译为可执行程序后(此时存储在磁盘中),已经有了虚拟地址。2.并且划分好了虚拟空间的区域

当程序要运行时,操作系统将代码与数据从磁盘加载到真实的物理内存中,并且建立相应的task_struct,task_struct中保存了页表。然后cpu执行内存中的代码,当要调用函数,或者读取变量的数据时,操作系统拿到的只是程序编译后形成的虚拟地址,所以cpu要到该进程的页表中通过拿到的虚拟地址查找真实地址,再到内存中查找真实地址继续执行代码。

所以cpu执行代码并不是直接通过真实地址,而是通过虚拟地址在页表中查找真实地址执行代码。就像学生的座号,座号只是虚拟的,学生的位置会发生改变,老师并不是通过教室的位置来识别学生,而是通过座号识别学生。学生就是进程,座号就是进程的虚拟地址,而学生的位置就是进程的真实地址。

fork为什么能返回两个值?

pid_t id = fork()

子进程和父进程共享代码,以上面那段代码为例,父进程执行fork函数时生成了子进程,父子进程的页表相同,也就是说在父子进程的页表中,id变量的虚拟地址映射了同一块物理内存。当子进程执行fork函数,fork最后执行return函数,id作为接收返回值的对象,要被fork函数的返回值修改,而父子进程的id存储在同一块内存空间,根据进程的独立性,子进程数据的修改不能影响父进程,所以这时会发生一个写时拷贝,哪个进程先调用fork的return,就更新哪个进程的页表,将id的虚拟地址映射到另一块物理内存中,再接受函数的返回值。所以,通过页表,父子进程的数据实现了分离,而两个地址一样的变量,却有不同的值,本质上是虚拟地址一样,物理地址不一样。

fork生成的子进程是否继承了父进程的所有代码?还是只继承了父进程fork之后的代码?虽然fork只执行父进程fork之后的代码,但子进程却继承了父进程的所有代码,子进程拷贝父进程中的pc(程序计数器,保存下一条要执行的指令),子进程根据pc执行代码,所以子进程只会执行fork之后的代码

有了虚拟地址空间的概念,可以更好的理解fork之后操作系统做了什么,操作系统生成了一个新的进程:创建了task_struct,mm_struct以及页表,由于进程具有独立性,子进程和父进程共享同一张页表,但数据以写时拷贝的方式进行修改,即其中一个进程的数据修改,其页表也要修改,不再指向和另一进程相同的地址空间。

为什么不能在创建子进程时将空间分离?

1.子进程不一定要进行空间的修改
2.每次fork都进行空间的分配,时间和内存的消耗高
3.只拷贝要被修改的数据是最理想的情况,但实现的难度高

写时拷贝变相的提高了内存的使用率。采用延时拷贝的策略,在需要修改数据时才分配空间给进程,虽然拷贝的成本依旧存在,但在修改这些数据之前,内存可以被其他进程使用,保证了内存的高效利用。

虚拟地址空间的意义

1.保证系统的安全性,比如代码中出现了野指针访问,并且这个访问将其他进程的数据修改了,这是一种极其危险的操作。而有了虚拟地址空间,操作系统就能查找该地址映射的真实物理地址,如果该地址没有映射物理地址,操作系统可以直接将野指针访问的操作拦截。在一定程度上保护了内存

2.更好地进行进程的管理。进程的页表中有许多区域:代码区,已初始化全局数据区,未初始化全局数据区…其中的堆区是动态管理的。比如你的程序需要100字节的内存,编译完代码后,操作系统不会直接将这100字节的内存给你,因为无法确定该程序会在什么时候被调用,直接给100字节内存很有可能造成空间的浪费,mm_struct中保存了每块区域的虚拟地址,(以[start,end]这样的区间保存),申请100字节的空间时,只需要对堆区的end+=100,操作系统才不会管你是否申请了内存,只会生成页表,当进程被调用时让这100字节虚拟空间映射到真实物理地址中。所以虚拟地址空间将进程与真实物理地址解耦,通过页表的映射,使进程的内存分配变得简单。

3.简化了进程本身的设计。虚拟地址空间使得每一个进程以统一的方式看待内存,这使得编译的工作变得简单,因为虚拟地址空间的每个区域都是固定的,边界明确的,局部变量存储在一块空间,正文代码存储在一块空间…操作系统只需要根据页表的映射执行代码。如果没有虚拟地址空间,数据的存储将杂乱无章,编译的工作将会变得十分困难

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

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

相关文章

python3GUI--音乐播放器(精简版)By:PyQt5(附下载地址)

文章目录一.前言二.预览1.主界面2.歌单页3.歌词页4.播放列表5.mini6.设置三.心得1.解耦2.体验优化3.歌词显示四.总结一.前言 传送门: 1.python3GUI–打造一款音乐播放器By:PyQt5(附下载地址&am…

LD_PRELOAD劫持

在前面UUCTF的uploadinject题,遇到了 LD_PRELOAD劫持,之前没遇见过,刚好借此机会学一学。不能小瞧这个变量,它甚至可以弹shell,绕过disable_functions,非常危险。下面来介绍一下这个变量,以及怎…

XDocReport使用入门

XDocReport 简介 XDocReport是GitHub上根据麻省理工学院许可证开源的Wrod导出框架。XDocReport可以根据ODT、Doc、Docx文档模板通过模板引擎语法(Freemarker、Velocity)转换为另外一种格式文档(Doc、Docx、XHTML、PDF)。 XDocR…

防沉迷管理系统

开发工具(eclipse/idea/vscode等): 数据库(sqlite/mysql/sqlserver等): 功能模块(请用文字描述,至少200字): 1、用户模块 1.1注册:用户通过注册生产账号,并在数据库存储数据 1.2登录:用户登录后…

k8s学习-CKA真题-k8s升级(kubeadm、kubelet、kubectl等)

目录题目解析命令准备工作升级组件升级kubectl、kubelet收尾结果killer 模拟环境题目解析解题参考题目 解析 结合博主当前环境,调整后题目为: 现有的 Kubernetes 集权正在运行的版本是 1.23.6,仅将主节点上的所有 kubernetes 控制面板和组件…

【语音处理】基于加权压力匹配方法(WPMM)的声音系统研究(Matlab代码实现)

👨‍🎓个人主页:研学社的博客 💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜…

【小程序】案例 - 本地生活(列表页面)

1. 演示页面效果以及主要功能 页面导航并传参 上拉触底时加载下一页数据 下拉刷新列表数据 2. 列表页面的 API 接口 以分页的形式,加载指定分类下商铺列表的数据: 接口地址 https://www.escook.cn/categories/:cate_id/shops URL 地址中的 :cate…

博泰应宜伦:智能汽车上攻时刻,需要“国家级”平台登场

作者 | 张祥威 编辑 | 王博汽车智能化转型的道路上,有个问题可能并非杞人忧天,而是值得整个行业警醒的。那就是: 中国的智能汽车发展,是否会被国外“卡脖子”? 卡脖子的担忧,其实也可以理解为,中…

【Linux】shell及其运行原理

目录1.什么是shell2.shell的功能3.shell的感性理解4.为什么不安装图形化界面1.什么是shell shell : 操作系统内核的外壳 通常来讲,计算机硬件是由运算器、控制器、存储器、输入/输出设备等硬件共同组成的,而让各种硬件设备各司其职且能协同运…

【基础强训】day3

一、选择题 &#x1f4a6;第1题&#xff1a; 以下程序的输出结果是&#xff08;&#xff09; #include <stdio.h> main() { char a[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, *p; int i; i 8; p a i; printf("%s\n", p - 3); } A 6 B 6789 C 6 D 789 B 先定义…

阳了之后,python实用工具之:疫情信息快速查看

嗨害大家好鸭&#xff01;我是小熊猫&#xff01;&#xff08;阳了个阳版&#xff09; &#xff08;先给大家消消毒&#xff09; 不好意思&#xff0c;很久没给大家更新了… 主要是小熊猫我不小心阳了… 大家要多注意自己的身体健康 多喝点热水 这个真的是个好东西 在家里稍…

深度学习—00入门 神经网络 pytorch

1、深度学习简介 深度学习是机器学习的一个分支&#xff0c;简单来说就是通过人工神经网络&#xff0c;强行在业务的 输入 和 输出 之间&#xff0c;暴力耦合一个出一个数学模型。 1.1 深度学习特点 1、由于是暴力耦合出来的模型&#xff0c;自然模型可解释性很差&#xff0c…

STM32 cubeMX配置OLED

文章目录前言一、OLED的接线二、cubeMX的配置三、OLED的驱动代码四、OLED的使用总结前言 本篇文章主要介绍OLED的操作和使用。 一、OLED的接线 OLED一共有四根线这里我使用的是IIC的OLED显示屏。 VCC----3.3V或者5V GND-----GND SDA-----PB7 SCL-----PB6 二、cubeMX的配置 …

TFT-LCD屏幕触摸校准

TFT-LCD屏幕触摸校准 触摸屏幕分类 1.电阻屏 电阻屏的主要部分是一块与显示器表面配合非常好的电阻薄膜屏&#xff0c;这是一种多层的复合薄膜&#xff0c;由一层玻璃或有机玻璃作为基层&#xff0c;表面涂有一层秀明的导电层&#xff0c;上面再盖有一层外表硬化处理、光滑防…

通达信量化接口需要被程序化执行吗?

其实通达信量化接口其实是量化交易模型的需要被执行的一种方式&#xff0c;但如果你交易者的策略模型采用比较中低频的交易执行方式&#xff0c;比如每天只交易一次&#xff0c;甚至每周或每月才轮动一次。那么小编认为通达信量化接口建议是否程序化执行也是一个非常重要的指标…

UI自动化测试-selenium元素定位

在使用Selenium和WebDriver进行UI自动化测试时&#xff0c;我们首先需要对元素定位&#xff0c;那么如何来定位元素呢&#xff1f; HTML 在进行元素定位之前&#xff0c;我们要对html代码有所了解。 <div classs_form><div classs_form_wrapper><div idlg>…

菜鸟程序员,被无良HR欺骗,因祸得福,竟“意外”拿下美团offer

前因后果 先讲述一下自己这次被无良HR欺骗坑惨的经历吧&#xff0c;面试的是一家上海某电商公司&#xff0c;给的薪资是不错。面试的时候&#xff0c;找我要了工资流水&#xff0c;然后给了我口头offer&#xff0c;就让我回去等了邮件了。回去之后就觉得offer没啥问题&#xff…

tomcat工作任务训练

文章目录1.安装jdk1.8 tomcat9.0 发布java项目部署java环境部署tomcat启动tomcat&#xff0c;测试访问上传项目安装数据库&#xff0c;建库建户web页面配置jpress测试写文章&#xff0c;上传附件2.tomcat多实例安装 比如 8080 8081 8082 发布3个论坛项目 war包也给你了tomcat多…

(memcpy memmove memcmp memset)内存操作函数详解

目录 &#x1f355;注意&#xff1a;内存操作函数隶属于头文件&#xff0c;因此在使用任何内存操作函数之前都必须引用 &#x1f95e;memcpy函数 &#x1f35e;memcpy函数的初步认识及使用 &#x1f373;样例示范&#xff1a; &#x1f9c8;代码呈现&#xff1a; &#x1f9c…

浏览器原理二三事

目录 1. 如何理解 JavaScript 是单线程的 2. 进程与线程 2.1 在浏览器中&#xff0c;如何理解进程和线程的关系&#xff1f; 2.2 浏览器的五种进程 2.2.1 浏览器主进程&#xff08;Browser 进程&#xff09; 2.2.2 浏览器渲染进程&#xff08;Renderer 进程&#xff09; …