1.操作 ./hello.o 中 .意思是执行在当前路径下的hello.o文件 ,是相对路径,不加.就是绝对路径,不存在这个文件
2.重新启动网络服务以应用更改,可以使用以下命令:sudo systemctl restart networking
3.
现在在主机局域网下建立NFS,让linux开发板在主机局域网中,并且开发板安装NFS客户端,连接局域网内的主机ip的NFS,并且将更新挂载在开发板指定的路径,这样,一旦路径中有变化,都会更新客户端和服务端
4.ls . 一个.表示当前目录,ls .. 两个.代表上一级目录
5.
文件描述符是一个整数,用于唯一标识打开的文件或I/O对象,它并不表示文件的地址,而是用于进行文件和I/O操作的抽象引用
6.makefile就是编译规则
7.linux中,文件都是存储在固态硬盘上的,相对ram读写较慢,所以c库函数中的fwrite函数会有缓冲 区的机制
具体原因:
在每次写入时,数据直接写入文件,这可能导致频繁的磁盘I/O操作。每次写入都需要与磁盘进行通信,这是相对较慢的操作,因此写入速度可能较慢。
数据首先写入到缓冲区,缓冲区会收集一定量的数据,然后一次性将这些数据写入文件。这减少了与磁盘的通信次数,从而提高了写入速度。缓冲区可以在内存中快速操作,比磁盘写入速度快得多。
上述示例中,使用缓冲区的写入操作通常会比不使用缓冲区的操作更快,因为它允许数据在内存中累积,减少了频繁的磁盘访问。这尤其在处理大量数据时更加明显,可以显著提高写入性能。但请注意,使用缓冲区可能会导致在文件关闭之前数据暂时存储在内存中,因此在关闭文件之前通常需要显式地刷新缓冲区(使用
fflush
函数)以确保数据被写入文件。
8.cat一个记录按键值文件的时候,会一直打印按键变化的内容,若不按按键即不上报事件,会一直等,不退出,因为这个文件是阻塞型的。
9.
当在shell解释器中输入 ./myprogram arg1 arg2 arg3 命令去执行可执行文件的时候,命令中是允许进行传参的。
如源文件是int main(int argc, char *argv[ ]) int argc 表示命令行参数的数量,包括程序名称,char *argv[] 是一个字符串数组,用于存储命令行参数的具体内容。这个命令输入后,argc 的值将为4,因为有4个参数(包括程序名称 ./myprogram),argv[0] 存储程序名称,即 "./myprogram",argv[1] 存储第一个参数 "arg1"。
argv[2] 存储第二个参数 "arg2"。argv[3] 存储第三个参数 "arg3"。
在C语言中,main 函数的参数是特殊的,它用来接收命令行参数,main 函数是程序的入口点,当你在命令行中运行一个C程序时,操作系统会将命令行中的参数传递给 main 函数,这是C语言的标准约定。
10.fork之后,子进程和父进程是一模一样的内容,只不过通过pid进行判断进入哪个分支。
11.alarm函数是通过硬件定时器中断中给进程发送信号实现的
12.进程捕获信号的过程:
内核执行进程的任务函数,在这期间,硬件发现错误或者用户输入等触发中断,在中断中执行相关信号编号的中断函数,将相应的信号标准置为,接着返回未执行完的进程,并且内核在某个时刻去查看是否收到了信号。所以就算进程感知实时信号也不需要轮询浪费算力资源,中断会解决问题。
13.进程是有自己的进程地址空间的,保存什么暂时未知
14.fork之后,子进程复制父进程的进程地址空间,里面包含父进程创建的匿名管道的文件描述符,所以子进程知道了文件描述符就也可以对管道读写了。
15.僵尸进程就是
指死了但没有被回收尸体的进程,也就是进程已经结束执行但是其进程描述符仍然存在,已经结束执行但是其进程描述符仍然存在,如果不及时回收僵尸进程,系统的进程表可能会被大量僵尸进程占用,最终导致资源耗尽。
16.FIFO有原子特性而PIPE没有,什么是原子特性呢?
系统会定义一个PIPE_BUF大小作为一个原子大小,一般为512字节,进程对文件写数据的时候,如果写入的数据量小于一个原子大小,他就不会被同时写入文件的进程干扰,保持让进程写完一个原子大小。
17.如果没有原子特性会发生什么?
比如有两个进程同时在写一个管道文件,A文件刚写入hel,B文件也写如tex,就导致写入到文件的根本是错乱的字符。
18.
临界资源" 是指在多个并发进程或线程中被共享的资源,在任何给定的时刻,只有一个进程(或线程)能够访问临界资源,而其他进程或线程必须等待,临界资源的管理通常需要使用同步机制,如互斥锁(mutex)或信号量(semaphore),以确保在任何时刻只有一个进程(线程)能够访问该资源,防止多个进程(线程)同时修改或读取资源,避免数据的不一致性和竞态条件。
19. "
线程是进程指令的执行体" 强调了线程是程序中执行实际指令的部分,而进程则是程序的整体实例,包含多个线程。线程可以协同工作,共享进程资源,以提高程序的效率和并发性。
20.linux中很多函数在执行错误的时候,会设置erro标志位,表示错误的情况,可以
通过perro()打印错误信息。
21.
脚本
脚本文件是以文本格式保存的,脚本文件是以文本格式保存的,由解释器按照脚本中的命令逐行执行。
Shell脚本: 使用bash、sh等Shell解释器执行。
Python脚本: 使用Python解释器执行。
22.
x86架构和ARm架构可执行文件不可以通用的原因
本质是不同架构cpu使用的指令集不一样,所以需要对不同架构cpu定制文件系统,软件工具,apt源也针对不同的架构提供不同的软件包,debian系统有支持多种架构的优势,本质就是因为该系统针对不同架构芯片做了不同的适配软件包。
23
.linux内核本身支持FAT32格式的文件系统,即该格式的u盘直接插就可以挂载,Ubuntu对多种文件系统都支持,因为Ubuntu发行版已经安装了相应的支持。
24.伪文件系统是在操作系统中的,不占用磁盘文件,占用内存
这些伪文件系统通常用于提供与内核和系统信息相关的数据,这使得它们非常适合用于提供实时系统信息,而无需存储在永久性存储介质上,这也有助于系统的高效性。
25.每个进程都有自己单独的地址空间(虚拟地址)
虚拟地址是由操作系统分配给进程的,这种虚拟地址到物理地址的映射过程是通过硬件的内存管理单元(MMU)来实现的,它执行地址转换,同时确保不同进程之间的内存互相隔离,从而提高了系统的稳定性和安全性,进程可以将其用于访问内存中的数据和指令,而无需关心物理内存的实际位置
硬件mmu实现虚拟地址和实际物理地址的转换,方便操作系统内核更好管理资源,更安全
26.程序(program)是一个普通文件,是为了完成特定任务而准备好的指令序列与数据的集合,保存在磁盘中,程序并不能单独执行,只有将程序加载到内存中,系统为他分配资源后才能够执行,
这种执行的程序称之为进程,进程是系统进行资源分配和调度的一个独立单位
27.
Linux系统中,程序只是个静态的文件,而进程是一个动态的实体,程序到底是如何变成一个进程的呢?
我们运行一个程序(可执行文件),通常在Shell中输入命令运行就可以了, 在这运行的过程中包含了程序到进程转换的过程,整个转换过程主要包含以下3个步骤:
1.查找命令对应程序文件的位置。
2.使用 fork()函数为启动一个新进程。
3.在新进程中调用 exec族函数装载程序文件,并执行程序文件中的main()函数。
28.在操作系统中,有两种主要的执行态:用户态和内核态。
用户态:在用户态下,程序只能访问自己的内存空间,不能直接访问硬件设备或者操作系统的内核数据结构。用户态的程序一般运行在一种受限制的环境中,以确保它们不会对系统的稳定性和安全性产生威胁。
内核态:内核态下,程序拥有更高的权限,可以访问系统的硬件设备、操作系统内核数据结构和执行特权指令。内核态通常由操作系统内核执行,以管理系统资源和提供系统调用接口。
29.PCB(进程控制块)是用于描述和管理进程状态以及保存与进程相关信息的数据结构。PCB 中存储的信息包括进程的程序计数器、寄存器、进程状态、进程优先级、进程所拥有的资源、进程的父进程和子进程等等。信号也是 PCB 中的一部分。
30.非实时信号是不支持排队的,这意味着如果在 PCB 中已经有一个标准信号等待处理,而另一个标准信号到来,那么后来的信号会覆盖之前的信号,导致之前的信号丢失。这是因为标准信号只能存储一个待处理的信号。实时信号则不同,它支持排队,每个到来的实时信号都会被存储在 PCB 中的信号队列中,而不会覆盖之前的信号。这意味着每个实时信号都能得到有效处理,且不会被丢弃,这就是为什么实时信号相对可靠,而
标准信号是不可靠的一个原因。
31.
同步信号:进程等待信号到来,然后去执行相应命令
异步信号:信号来的情况未知,不管他何时来,我某时才看看进程来了没,来了就处理
32.进程申请信号的捕获使用signal函数,当内核在运行该进程的时候,才会在中断中捕捉到
该信号,并且执行信号中断服务函数,如果该进程没有执行或者被挂起,就捕捉不到该信号。因为当用户输入就触发中断,系统当前有一份专属于正在运行的进程级的信号中断向量表,所以,有中断发生,只会相应当前进程要求捕获的信号
33.信号给进程间传信号信息,而,
管道可以在不同进程间传数据信息包括文本,字符串,数字等等
34.在shell进程中输入并执行命令时,通常会创建一个子进程来执行该命令。这个子进程会继承父 shell 进程的环境,执行命令,然后在命令执行完毕后退出。
35.linux中的文件都是保存到磁盘的,不是内存
36.ps -aux | grep root 这个命令使用了管道,可以
把管道想象成特殊的文件,ps进程对管道文件写,grep进程对文件进行读,但是管道文件不是真实的文件,他是占用一块内存空间
37.有名管道和消息队列的区别
通信方式:
有名管道:有名管道是一种半双工通信方式,只支持单向通信。这意味着数据只能在一个方向上传输,例如从一个进程到另一个进程,但不能同时在两个方向上传输数据。
消息队列:消息队列是一种全双工通信方式,支持双向通信。进程可以同时向队列发送消息和接收消息。这使得它更适合于需要相互通信的进程之间的应用程序。
数据结构:
有名管道:有名管道实际上是一个特殊文件,保存在文件系统中。数据通过读取和写入文件来传递。它们通常以文件路径的形式存息队列。
消息队列是一个内核管理的数据结构,与文件系统无关。消息队列使用消息缓冲区来存储数据,每个消息都有一个类型标识符在。
持久性:
有名管道:有名管道在文件系统中存在,但当没有进程使用它们时,它们会被删除。它们通常不是持久性的,只在进程使用它们时才存在。
消息队列:消息队列是持久性的,它们在内核中维护,即使没有进程连接,消息队列也会存在。这使得它们更适合于长期运行的应用程序。
数据传输方式:
有名管道:有名管道通常以字节流的方式传输数据,没有消息的概念。数据的传输是无结构的,
这意味着数据在管道中没有明确的结构或边界,而是一个连续的字节序列。这限制了其能够传输的数据类型和结构,
通常用于传输文本数据、原始字节流或二进制数据。由于没有消息类型的概念,通常用于简单的数据传输,例如文本文件、命令的输出等。
消息队列:消息队列使用消息的概念,每个消息都有一个类型和一个正文。这使得它更适合于结构化数据的传输,
消息队列非常适合传输多种数据类型,包括结构化数据、对象、自定义消息等。这使得它更适合于复杂的数据交换。
总之,有名管道适合一方向、无结构的数据传输,而消息队列更适合于双向通信和结构化数据的传输,同时具有更多的灵活性。
38.线程的调度策略:
分时调度策略(SCHED_OTHER):这是默认的线程调度策略,适用于大多数常规应用程序。它基于时间片轮转的原则,为每个线程分配一个小的时间片,然后在时间片用尽后切换到下一个线程。分时调度确保了公平性,每个线程都有机会获得CPU时间。它通常用于非实时性应用,如桌面应用和通用服务器。
实时调度策略,先进先出方式调度(SCHED_FIFO):这是一种实时调度策略,通常需要超级用户权限才能使用。在SCHED_FIFO策略下,每个线程被分配一个优先级,并且较高优先级的线程始终优先执行,直到它们主动放弃CPU或更高优先级的线程到达。这种方式确保了高优先级任务的及时执行,但要小心,因为过于占用CPU的高优先级任务可能会导致低优先级任务饥饿。
实时调度策略,时间片轮转方式调度(SCHED_RR):与SCHED_FIFO相似,SCHED_RR也是一种实时调度策略,通常需要超级用户权限。每个线程被分配一个优先级,并且一个时间片(或时间配额)。当线程的时间片用尽时,它将被移到就绪队列的末尾,然后调度器将选择下一个线程执行。这样,时间片的轮转确保了较高优先级的线程不会永远占用CPU,以便其他线程也能获得机会执行。这种策略保证了相对公平的调度。
总之,这些调度策略适用于不同类型的应用和需求。SCHED_OTHER适用于通用用途,SCHED_FIFO和SCHED_RR适用于需要实时性能的应用,但需要小心使用以确保系统的稳定性和公平性。 SCHED_RR比SCHED_FIFO更具公平性,因为它为每个线程分配时间片,确保了更高优先级任务不会完全阻塞较低优先级任务。
进程也有调度算法。
39.线程的优先级:
静态优先级:静态优先级是一种线程或进程的基本优先级,它在创建时分配给线程,并不会随着线程的运行而自动改变。静态优先级通常在实时调度策略中使用,如SCHED_FIFO或SCHED_RR。这些策略要求线程明确设置其静态优先级,这将决定线程在就绪队列中的调度次序。
动态优先级:动态优先级是一种根据线程的行为而自动调整的优先级。在Linux中,它通常与默认的分时调度策略(SCHED_OTHER)一起使用。动态优先级会根据线程的nice值和其行为来进行调整。如果线程被调度器调度无视(例如,由于其他高优先级线程在运行),其动态优先级可能会自动增加,以提供更公平的CPU时间分配。
实时调度策略:实时调度策略如SCHED_FIFO和SCHED_RR使用静态优先级,静态优先级决定了线程在就绪队列中的调度次序。这些策略要求线程显示设置静态优先级,以确保高优先级任务在需要时优先执行。
总之,静态优先级是为实时调度策略而设计的,是线程或进程创建时分配的基本优先级,通常不会改变。而动态优先级是用于分时调度策略,根据线程的行为和nice值来进行自动调整,以确保公平性和资源共享。不同的调度策略使用不同类型的优先级以满足不同类型的需求。
40.线程有
线程栈,用来保存函数形参、局部变量、线程切换现场寄存器等数据,线程使用的是进程的内存空间,那么就有可能进程的内存空间是不够的,某些线程可能需要完成很大量的工作,或者线程调用的函数会分配很大的局部变量, 亦或是函数调用层次很深时,需要的栈空间可能会很大。
41.在
同一个进程中的多个线程通常共享相同的内存空间和数据段。这是因为在单个进程中的所有线程都属于同一个执行上下文,它们可以访问相同的全局变量、静态变量、堆内存和共享库等。这是多线程编程的一项特性,但也需要开发人员小心处理以避免潜在的问题。
共享内存: 多个线程可以同时访问和修改相同的内存区域。这可以用于共享数据,但也需要进行适当的同步,以避免数据竞争和不一致性。
全局变量和静态变量: 所有线程都可以访问相同的全局变量和静态变量。这使得全局数据在多个线程之间共享,但也需要进行线程安全处理,以避免并发问题。
堆内存: 所有线程可以访问相同的堆内存区域,这是通过动态分配内存(例如malloc或new)获得的内存。多线程需要小心避免内存泄漏和悬挂指针等问题。
共享库: 多线程程序通常会使用共享库,这些库的代码段在多个线程之间共享。这有助于节省内存,但需要确保库的线程安全性。
在多线程编程中,共享数据段的特性可以提高线程之间的通信和数据共享效率。然而,这也带来了潜在的问题,如数据竞争、死锁和内存管理挑战。因此,在多线程编程中,需要小心设计和同步线程,以确保数据的一致性和程序的正确性。
42.
进程中各个线程的运行是相互独立的,线程的终止并不会相互通知,也不会影响其他的线程,
终止的线程所占用的资源不会随着线程的终止而归还系统,而是仍为线程所在的进程持有
, 这是因为一个进程中的多个线程是共享数据段的。
43.
POSIX互斥锁的动态初始化和静态初始化的区别:
静态初始化:
静态初始化是在编译时进行的,不需要在运行时动态分配内存。
静态初始化使用PTHREAD_MUTEX_INITIALIZER宏来创建互斥锁。
静态初始化创建的互斥锁通常具有较低的内存开销,因为它们不需要额外的动态内存分配。
静态初始化创建的互斥锁在定义时分配,通常位于全局或静态作用域,因此可以在整个程序中访问。
动态初始化:
动态初始化是在运行时通过函数调用进行的,需要动态分配内存。
动态初始化需要调用pthread_mutex_init函数来创建和初始化互斥锁。
动态初始化创建的互斥锁通常具有较高的内存开销,因为它们需要额外的内存来存储互斥锁的状态信息。
动态初始化创建的互斥锁可以在运行时动态分配和销毁,因此更灵活,但需要开发人员手动管理内存。
总之,静态初始化和动态初始化都可以用来创建互斥锁,但它们在内存分配、初始化方式和生命周期管理方面有不同的特点。开发人员可以根据程序的需求选择适当的初始化方式。静态初始化适用于需要在整个程序生命周期内使用的互斥锁,而动态初始化更适用于需要在运行时创建和销毁的互斥锁。
44.
操作系统中,程序运行的空间分为内核空间和用户空间
,对于这句话的理解:
用户空间(User Space):
用户空间是分配给用户程序和应用程序的内存区域。在这个区域内,应用程序可以运行,访问和操作非特权的内存和资源。
大多数应用程序运行在用户空间,包括文档编辑器、浏览器、媒体播放器、游戏等。用户程序通常没有直接访问硬件或操作系统内核的特权。
内核空间(Kernel Space):
内核空间是分配给操作系统内核的内存区域。内核是操作系统的核心部分,它负责管理系统硬件、文件系统、内存管理、进程调度、设备驱动程序等。
内核空间中运行的代码具有更高的特权级别,可以访问和操作系统的核心资源和硬件设备。这包括处理中断、调度进程、管理内存、执行系统调用等。
为什么要划分内核空间和用户空间?
这种划分是为了实现操作系统的安全性和稳定性,以及隔离不同应用程序之间的操作。它有以下优点:
隔离和保护: 内核空间和用户空间的划分可以确保用户程序不能直接访问和操作内核代码和数据。这有助于防止用户程序对操作系统造成不良影响,提高了系统的稳定性和安全性。
特权分离: 内核空间中运行的代码具有更高的特权级别,这是必要的,因为操作系统需要访问硬件设备和执行敏感的系统操作。将内核与用户空间分开有助于维护这种特权分离。
错误隔离: 如果用户程序中存在错误或崩溃,它们通常不会影响整个系统。操作系统内核通常能够处理错误并确保系统的继续运行。
总之,内核空间和用户空间的划分是操作系统设计的关键部分,它有助于确保系统的安全性、稳定性和隔离性。内核空间用于管理和控制硬件,而用户空间用于运行应用程序,这种隔离对于多任务处理和多用户系统至关重要。
就是将整个内存划分两部分,一部分用作用户,一部分用作内核。
45.
进程需要修改共享内存中的某个页面时,只会复制并修改那个具体的页面,而不是整个地址空间。这是
COW(Copy On Write)写时复制技术的工作原理。
具体来说,COW将父进程和子进程的地址空间指向相同的物理内存页面。当父子进程中任何一个尝试修改这个页面时,操作系统会进行页面复制。这意味着只有需要修改的页面才会被复制,而其他页面仍然保持共享状态。
这种方式确保了内存的高效使用,因为只有在真正需要独立拷贝时才会进行拷贝操作,而不是在每个页面都被复制的情况下。
46.
进程的页表:
每个进程在被新建的时候都会给他分配一个页表(是一种数据结构),页表包含了整个父进程的虚拟地址空间。这包括进程的代码、数据、堆和栈等。每个进程都有自己的虚拟地址空间,其中包含了该进程的所有数据和指令(代码)。页表的任务是将这些虚拟地址映射到实际的物理内存地址,以便进程可以正常访问其需要的数据和指令。
47.当我用c写完一个脚本后,该文件就算一个
文本文件,是不可以被执行的,如果需要执行该脚本,需要使用命令(自动/手动)将该文本文件编译成二进制机器码的可执行文件,该文件是有可执行权限的,因为他是机器码文件(可执行的文件)
而文本文件是没有可执行权限的,可执行权限指示了操作系统可以直接执行这个文件,而不需要编译它。
48.bash解释器工作原理(为什么叫解释器):
当你在终端输入命令,比如说ls,首先,Bash检查命令是否为内置命令(例如,cd),如果不是内置命令,Bash会在$PATH环境变量中定义的一系列目录中搜索可执行文件,接着, 找到了 ls 命令的可执行文件后,Bash创建
一个子进程来执行这个命令(交给内核执行了)。
总结:bash解释器指的是,找命令文件,然后交给内核去执行(这就是解释的意思)
49.自己用c写了一个脚本文件,这个脚本文件并没有被编译成二进制文件,我用
source执行脚本文件,系统执行的流程:(还不确定)
1.新建一个子进程执行
1.Shell解析命令: 当你输入source script.c时,Shell解析这个命令并识别出你想要执行一个脚本文件。
2.打开并读取源码文件: Shell会尝试打开指定的源码文件 "script.c",然后读取它的内容,如果源码文件中包含C语言代码,Shell会将其交给C编译器(通常是GCC)进行编译。编译器会将C代码转换为可执行文件,c的可执行文件再和脚本中shell命令的可执行文件链接起来,并将其加载到内存中。
3
.创建一个新的进程,在该进程中运行编译后的可执行文件。
4.由于子进程和父进程是共享内存空间的,所以子进程运行的可执行文件中的输出(例如printf语句的输出)将显示在当前终端上。此外,如果脚本或可执行文件执行了文件操作或其他系统调用,这些操作将影响当前Shell环境。
50.bash脚本:
包含一系列Bash命令和脚本语法,这些命令和语法由Bash解释器执行。当你在终端中运行一个Bash脚本时,Bash解释器会逐行解释并执行脚本中的命令。注意:与二进制可执行文件不同,Bash脚本文件不需要编译为机器代码,因为Bash解释器能够直接理解和执行Bash脚本中的命令。所以,你可以直接运行Bash脚本而无需编译。bash是解释型语言(如Bash或Python)
c为什么要编译?
C语言是一种编译型语言,它的源代码需要经过编译器的处理,将源代码翻译成机器代码。这个过程会生成一个独立的二进制可执行文件,其中包含了计算机可以直接执行的机器指令。
编译后的程序通常比解释型语言的脚本执行速度更快,因为它们已经被转换为机器代码,不需要在运行时解释。
51.source执行bash脚本文件,系统执行的流程:
解释器选择:系统会
启动一个新的Bash解释器。这个解释器将执行脚本中的命令。
打开文件:解释器会打开指定的脚本文件。
逐行执行:解释器会逐行执行脚本文件中的命令。它会执行文件中的每个命令,就好像你在命令行中逐个输入它们一样。
环境变量:脚本可以访问和修改当前shell的环境变量。这意味着脚本可以读取并修改当前shell中的环境设置。
执行完毕:一旦脚本的所有命令都执行完毕,解释器将退出。
恢复到原始环境:在 source 命令之后,你的当前shell会继续在原始环境中运行,没有新的进程或子shell被创建。任何在脚本中修改的环境变量或工作目录都会影响你的当前shell。
总之,使用 source 命令来执行脚本文件时,
脚本会在当前shell环境中运行,而不是在一个新的子shell中,这意味着它可以直接影响你的当前工作环境。这对于在当前shell中设置环境变量或执行配置脚本等任务非常有用。
52.前台进程通常指的是当前用户正在与之交互的进程,当你在终端中运行一个命令时,该命令通常成为前台进程,而它的输出和输入会显示在当前终端窗口中。在这种情况下,你可以使用键盘输入命令,同时你也可以看到命令的输出。如果你按下Ctrl+C,通常会发送中断信号(SIGINT)给前台进程,从而中断它的执行。与之相反,后台进程是在后台运行的进程,它们不会在终端窗口中显示输出,通常是通过在命令后加上&来启动的,这样它们就会在后台运行。
所以,如果你想发送一个信号给一个不在前台的进程,你通常需要使用kill命令或其他方式来指定进程的标识符(PID),因为你无法直接在终端中与它交互。
kill命令可以用于发送信号给前台进程和后台进程。你可以使用kill命令来向任何正在运行的进程发送信号,包括前台进程和后台进程。
53.
同步,异步的解释:
同步:
等待机制: 在同步通信中,发送方通常会等待接收方明确的信号或确认,以确保数据的可靠传输。这种等待机制可以确保数据不会被丢失或产生混乱。
阻塞式通信: 同步通信可能会导致阻塞,因为发送方可能需要等待接收方的响应或确认。这种阻塞可以保证数据传输的可靠性,但也可能导致效率降低。
同步通信通常适用于需要可靠性和确定性的场景,但它可能会导致一些效率问题。
异步:
异步信号可以随时产生,而进程不需要等待或轮询来接收它们。
信号处理: 进程可以注册信号处理程序(也称为信号处理函数),用于捕获和处理特定类型的信号。异步信号有回调函数的原因是,进程不会及时处理信号,所以某个时刻要执行了,再去执行回调函数。
54.
错误信号一般是同步信号的原因:
错误信号通常是在程序执行中出现错误或异常情况时生成的,它们表示了程序内部或外部环境中的问题,虽然错误可以随时发生,但错误信号的重要特征是它们需要立即处理,以应对错误或异常情况。这里的“同步”表示在信号处理方面的即时性和紧迫性。这种要求使它们具有“同步”的特性,因为它们的处理不会被推迟,而是在发生错误时立即触发。
55.当系统执行一个脚本后,为什么会shell创建一个新的进程去执行脚本,而不是新的一个线程?
这是因为进程和线程是不同的执行单元,它们具有不同的特性和适用场景。以下是一些原因:
独立性:进程是相互独立的执行单元。每个进程都有自己的独立地址空间,文件描述符表,和资源,这使得进程之间更为隔离。这是非常重要的,特别是在安全性和稳定性方面。如果一个进程崩溃,不会影响其他进程。
并行性:进程在多核系统上能够并行执行,每个进程可以分配到不同的CPU核心上执行,从而提高系统的性能。线程通常在同一个进程内共享相同的地址空间,因此不如进程那样容易实现真正的并行性。
错误隔离:如果脚本中的一个进程出现问题(例如无限循环或崩溃),它不会影响系统中的其他进程或线程。而在共享内存的多线程环境中,一个线程的错误可能会对其他线程产生负面影响。
资源管理:进程提供了更好的资源管理。每个进程都有其自己的资源上下文,这意味着操作系统可以更轻松地管理进程的资源分配,如内存和文件句柄。线程共享相同的资源上下文,这可能会导致资源争用和更复杂的管理。
虽然进程的创建和切换通常比线程更昂贵,但选择创建新进程而不是线程是有意义的,特别是在需要隔离性、独立性和并行性的情况下。线程通常更适合共享相同上下文和数据的情况,以提高性能,但需要小心处理线程间同步和竞态条件
56.system V信号量和POSIX信号量什么区别?
System V 信号量是一种早期的 IPC 机制,POSIX 信号量是更现代的 IPC 机制,它符合 POSIX 标准。
System V 信号量需要显式地创建和初始化,并且通常需要程序员手动指定键(key)以唯一标识信号量。
POSIX 信号量可以使用 sem_init 函数轻松创建和初始化,无需关心键的分配和管理。
System V 信号量可以使用键(key)来命名,但不支持命名空间,因此可能容易发生命名冲突。
POSIX 信号量支持有名和无名(unnamed)两种类型。有名信号量有独立的命名空间,不容易发生命名冲突。
总的来说,POSIX 信号量通常更易于使用和移植,而 System V 信号量是一种较早的 IPC 机制,在某些情况下仍然有其用途。
57.有一个硬链接文件,如果原始文件被删除,会怎么样?
一原始个文件,有一个硬链接文件,如果原始文件被删除,硬链接将继续有效,删除文件实际上只会删除一个链接计数,而不会立即删除数据或inode。只有当链接计数降为零时,文件系统才会释放相关的inode和数据块。硬链接在文件系统中是多个目录项指向相同的inode,当您删除一个硬链接时,只是减少了链接计数。所以,删除 原始文件不会影响硬链接的可用性。您仍然可以通过硬链接访问文件的内容。
58.什么是挂载?我在某个目录下创建一个文件,也算是将文件挂载吗?
在Linux中,创建一个文件不等同于挂载文件系统。挂载是将一个文件系统(通常是磁盘分区或网络文件系统)连接到文件系统树的某个目录,使其可用于读取和写入文件。创建一个文件只是在文件系统中的目录中创建一个新文件,而不涉及挂载整个文件系统。注意:磁盘分区(内部是有文件系统的)或网络文件系统都是一个文件系统,挂载的是这一整个文件系统。
当你创建一个文件,它只是在现有的文件系统中分配了一些磁盘空间来存储该文件的内容,而不会改变文件系统的结构或添加新的文件系统。
挂载文件系统涉及到将一个完整的文件系统添加到系统中,允许你在挂载点上访问该文件系统的内容。这通常用于连接硬盘分区、USB驱动器、网络共享或其他存储设备,以便你可以在本地文件系统中访问这些设备的内容。
总之,创建一个文件和挂载一个文件系统是不同的操作,具有不同的影响和用途。创建文件只是在一个文件系统中创建一个新文件,而挂载文件系统是将一个完整的文件系统添加到系统中,允许访问其中的多个文件和目录。
59.挂载U盘
在我们插入一个u盘的时候,会出现/dev/sda这个设备,但是现在并没有挂载在目录树,/dev/sda这个路径只是设备的名字,只能看的有这个文件,但是无法操作,我们需要挂载在目录树中的某个路径,这时候才可以访问该u盘
60.bash是解释器,终端是类似于可以执行bash的一个输入程序?
Bash 是一种命令行解释器,它可以解释并执行用户输入的命令。
终端(Terminal)或终端仿真器是一个提供命令行界面的应用程序,它允许用户在文本模式下输入命令,这些命令会被发送给解释器(如 Bash)进行处理。
你可以将终端视为一个容器,它提供了一个与操作系统通信的文本界面,用户可以在其中运行命令。用户在终端中输入的命令会由 Bash 这样的解释器进行解释和执行。终端提供了一个用户与计算机进行交互的方式,而 Bash 是执行命令的实际引擎。
61.系统所需的全局变量(PATH,MAIL等等)在上电后,是怎么设置的?
是因为有环境配置文件,bash在启动后去执行该脚本,完成环境变量的配置
62.$(命令)
是一种命令替换的语法形式,这个语法的作用是执行括号内的命令,并将其标准输出插入到外部命令中
如:result=$(ls -l) 执行ls -l命令,然后将其输出赋值给变量result
63.$(( ))
$(( )) 是 Bash 中用于进行算术运算的语法。它允许在括号内执行数学运算,并将结果赋值给变量。例如,$((s + 1))
$() 则是用于命令替换的语法,它执行括号内的命令,并将其输出作为字符串返回。
64.可以for ((i=0; i<4; i++)) 但是 while一般不使用while (( ${a} < 2 ))
在 for 循环中,$(( )) 通常用于进行迭代变量的数学运算,例如递增或递减。而在 while 循环中,条件判断一般使用 [ ] 或 [[ ]] 这样的表达式,而不是 $(( ))
注意:(( )) 是 Bash 特有的语法,因此如果你的脚本需要在其他 shell 中运行,可能需要考虑使用 [ ] 这种更通用的条件判断语法。
65.写c文件,gcc编译后不输出printf
printf("Hello, World!\n"); 要像这样
![](https://i-blog.csdnimg.cn/blog_migrate/6afc53ffccda8e64b062e6a961e2bfa2.png)
66.各个架构的arm内核对应的ARM指令集就是说是ARM出的一套规则,这个规则出了各个汇编指令和机器码的对应关系
67.添加镜像源
进入到各个国内镜像源网站,然后选择ubuntu版本
查看我们板子的ubuntu版本:
-
cat /etc/issue
选择相应版本
![](https://i-blog.csdnimg.cn/blog_migrate/8fdc54410a610510063a59522c899e55.png)
每个版本会有一堆该命令
我们在linux中用管理员权限使用编辑器打开
/etc/apt/sources.list,把该文件备份,清空
然后复制上面的那堆配置
然后:sudo apt update
如果出现密钥错误,这样做:
![](https://i-blog.csdnimg.cn/blog_migrate/454c537ffc3919fb33e05e7ac37f0c9a.png)
gpg --keyserver
hkp://keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
![](https://i-blog.csdnimg.cn/blog_migrate/6b99315b5f4bf6376c530fa715c8ee73.png)
gpg --export --armor 3B4FE6ACC0B21F32 | sudo apt-key add - (就是这样,没有少)
68.
insmod 命令和module_init函数的关系
insmod负责将模块代码在某时载入内核空间,module_init定义了在执行insmod命令时内核应当执行的函数,没有insmod或modprobe命令,内核是不会知道何时以及如何加载和初始化模块的。
69.
将驱动变成内核模块的优缺点
优点:
性能更高,可以直接访问硬件资源,减少了用户空间到内核空间的切换
实时性更好:内核模块可以更好地支持实时操作,对于需要高实时性的应用更为合适。
集成度高:直接集成在内核中,不需要通过外部程序来控制,一旦该模块被加载,它就直接成为了内核的一部分。意味着用户和应用程序可以像使用任何其他已支持的工具一样使用它,无需任何外部程序来“控制”或“调用”它。
缺点:
维护困难:一旦内核升级,可能需要对模块进行相应的修改和适配。
安全风险:错误的代码可能导致系统崩溃或安全漏洞。
70.
将驱动只是作为一个c文件去执行控制方案的优缺点
优点:
灵活性高:可以很容易地通过参数传递等方式修改控制逻辑,无需重新编译或加载内核模块。
安全性更好:运行在用户空间,即使程序崩溃也不会影响到系统的稳定性。
缺点:
性能较低:每次操作都需要通过系统调用从用户空间切换到内核空间,增加了开销。
实时性差:与内核模块相比,用户空间程序的实时性不够好,可能无法满足一些高实时性要求的应用。
71内核符号
是指那些在内核代码中定义并且可以被内核自身的其他部分或是加载到内核中的模块(例如设备驱动程序)所引用的
变量或函数。内核符号使得
模块化的内核设计成为可能,允许动态地在运行时加载或卸载功能模块,而不需要重新编译整个内核。这样,系统可以根据需要扩展功能,同时保持高效和灵活。
内核符号的主要作用包括:
模块间通信:允许内核模块之间共享代码和数据结构
动态加载和卸载:使得内核能够在不重启的情况下动态地加载或卸载功能模块
内核空间和用户空间隔离:内核符号机制有助于保持内核空间和用户空间的分离,从而提高系统的安全性和稳定性。(内核符号机制通过控制哪些函数和变量可以从内核空间暴露给用户空间,有助于保持内核空间和用户空间的分离。)
72Linux提供了一些工具和文件来管理和查找内核符号,例如:
/proc/kallsyms:这个文件列出了当前运行内核的所有符号及其地址。它对于调试和分析内核行为非常有用。
nm:这个命令行工具可以用来显示二进制文件(包括内核模块)中定义的符号。
modinfo:这个工具可以显示内核模块的信息,包括模块中导出的符号。
73编写的内核模块运行在内核空间,而不是用户空间
74Linux的内核符号表是一种数据结构,用于存储内核中所有符号(例如函数和变量)的地址和名称。这个表是内核和模块之间通信的关键,使得模块能够在不需要重新编译内核的情况下使用内核中定义的函数和变量。
75.在Linux中看到的所有文件和目录,无论它们实际存储在什么类型的文件系统上(比如ext4、XFS、Btrfs等),都是通过
虚拟文件系统(VFS)实现和访问的。VFS为操作系统提供了一个统一的文件系统操作接口,允许用户空间的应用程序以一致的方式访问不同的底层存储设备和文件系统。(open、write)函数
76设备节点
设备节点(Device Node)是一种特殊的文件,这些文件位于/dev目录下,代表系统中的设备,如硬盘、终端、打印机等。设备节点是“一切皆文件”哲学的体现,它允许应用程序通过标准的文件操作API(如open、read、write、close)来访问和控制硬件设备,而不需要直接操作硬件。
设备节点主要有两种类型:
字符设备:这类设备以字符为单位进行数据传输,允许对设备进行随机访问。字符设备通常不支持数据的缓冲。例如,键盘、鼠标和串口都是字符设备。
块设备:这类设备以数据块(通常是512字节或更多)为单位进行数据传输,支持随机访问,并且可以缓冲数据。硬盘和光盘驱动器是典型的块设备。
设备节点通常在设备驱动程序加载到内核时自动创建,但也可以手动通过mknod命令创建。
例如:创建串口设备节点后,打开设备文件,写设备文件,就实现串口发送了
77在Linux系统中,网络设备与字符设备或块设备的处理方式有所不同,因此
网络设备通常不通过设备节点(如/dev目录下的文件)来访问。相反,网络设备的访问和控制主要通过网络接口和套接字(Sockets)API来实现。
为什么网络设备不使用设备节点?
网络通信的复杂性和动态性要求更高级的抽象和灵活的数据传输机制。网络设备涉及到的数据流通常是双向的、异步的,并且可能涉及到复杂的协议栈处理(如TCP/IP协议)。套接字API提供了这种灵活性,允许应用程序在不同的网络层之间进行通信,而不是通过简单的读写操作来与设备交云通。
此外,网络设备的配置和管理通常比字符设备或块设备更为复杂,涉及到的操作包括路由设置、网络协议配置、安全设置等,这些都超出了传统设备节点所能提供的接口范围。
78.字符设备的注册与使用
假设我们有一个简单的字符设备(比如一个虚拟的LED设备),设备驱动程序需要执行以下步骤:
定义file_operations结构体:指定设备支持的操作,例如实现open、read、write等函数。
分配设备编号:通过alloc_chrdev_region函数为设备分配主设备号和次设备号。
初始化cdev结构体:并将其添加到系统中,关联步骤1中定义的file_operations结构体。
创建设备节点(生成设备节点文件):通常使用mknod命令或udev系统自动创建。
当用户程序想要操作这个设备时,它会打开对应的设备文件(比如/dev/myled)。这时,内核根据设备文件的inode找到对应的cdev对象,进而找到设备的file_operations结构体,最终通过调用这里面的函数来操作硬件设备。
79.cdev结构体
Linux内核使用cdev结构体来表示字符设备。这个结构体包含了设备的一些基本信息,包括设备编号(主设备号和次设备号组合)和指向file_operations结构体的指针
inode结构体和file结构体
inode结构体:表示文件系统中的一个索引节点,包含了文件的元数据。对于设备文件,它包含了设备的主次设备号,以及指向设备操作函数的指针。
file结构体:当用户程序打开一个设备文件时,内核会为该操作创建一个file结构体实例,它包含了文件操作的当前状态信息,如文件指针的位置,以及指向inode结构体的指针。
file_operations结构体
这个结构体定义了一系列函数指针,对应于设备支持的操作。每当用户程序通过设备文件执行操作(如读取数据),内核就会调用file_operations结构体中相应的函数来实际执行操作。
80.初始化cdev结构体:并将其添加到系统中,这句话中的将其添加到系统中怎么理解?怎么添加到系统中的?
为cdev结构体分配内存
然后,使用cdev_init或手动方式来初始化这个结构体
分配设备号,并将其注册到内核中(注册过程使内核能够识别和管理新的字符设备。内核维护了一个设备列表,cdev_add调用会将你的设备添加到这个列表中。这意味着内核现在知道了这个设备的存在,并且知道如何通过分配给它的设备号来找到它。)
cdev已经添加到系统中,用户空间程序还需要一个设备文件来访问这个设备,那就是创建设备文件,设备文件与cdev结构体通过设备号相关联,使得对设备文件的操作能够被映射到cdev的文件操作上。
81.Linux中,
每个文件(包括设备文件)都通过一个inode结构体来表示。这个inode结构体包含了文件的元数据,比如文件类型、权限、大小、所在磁盘的位置等信息。对于设备文件来说,inode还存储了设备的主设备号和次设备号,这是链接到具体设备驱动和设备实例的关键信息。
当一个文件(无论是常规文件还是特殊的设备文件)被访问时,文件系统负责解析路径名,查找对应的inode,并根据inode中的信息进行相应的操作。这是一个统一的机制,适用于所有类型的文件。
82.Linux中,命令通常可以分为以下几类:
内建命令:
这些是shell的一部分,不需要调用外部程序来执行,如:cd,echo,export等,内建命令通常执行得更快,因为它们不涉及新进程的创建。
外部命令:
这些命令存在于文件系统中的某个位置,通常是系统的PATH环境变量定义的目录中,如/bin、/usr/bin等。当你输入命令时,shell会在PATH指定的目录中查找可执行文件,并且创建当前进程的副本shell进程去执行命令,由于子进程继承了父进程的文件描述符,外部命令的输出(通过stdout)自然而然地流回到了启动它的那个shell进程的终端或控制台中,这就是你能在当前shell中看到命令输出的原因。
Shell 函数:
用户或系统管理员可以编写的小段代码,用于执行特定的任务。
它们在当前shell会话中运行,不需要创建新的进程。
Shell函数是一小段可以在shell中执行的代码,它允许你将多条命令组合成一个单独的命令来执行。函数通常用于封装重复使用的逻辑或命令序列。
函数可以像任何其他命令一样被调用。函数在被调用时执行,可以接受参数,并在当前shell环境中运行,而不是启动一个新的shell进程。(和shell脚本相似,但是使用场景不同,Shell脚本是一个包含一系列命令的文本文件,当运行该文件时,shell逐一执行文件中的命令。脚本可以包含变量、控制流语句(如if-else、循环等)、函数调用等复杂逻辑。
脚本通常用于自动化重复的任务、系统管理任务、批量处理等。)
别名:
在shell中被用作快捷方式,以避免键入长串命令。
关键字:
这些是由shell解释的特殊单词,用于控制命令的行为,通常是shell编程中的语法元素。
例如:if、then、else、fi、case、esac、do、done、for、while等。
83.外部命
令执行时会创建新进程,原因:
隔离和安全:
每个进程在操作系统中都有自己的内存空间和资源。这种隔离确保了一个进程不能直接访问或干扰另一个进程的内存和资源。如果一个外部命令在当前shell进程中直接执行,它就能访问shell的内存空间,这可能会引起安全问题或数据损坏。
资源管理:
操作系统可以对每个进程进行资源分配和管理。例如,它可以限制某个进程的CPU使用率或内存占用,或者在进程结束时回收资源。
并发执行:
创建新的进程允许系统在多核处理器上实现并行执行,即使是在单核处理器上,多任务操作系统也可以通过时间片轮转等技术,让多个进程看似同时运行。
84.sudo提升命令权限的原理:
使用sudo来执行一个外部命令时,sudo实际上是提升了创建用来执行该命令的子进程的权限,让这个子进程以超级用户(root)的权限运行。这样,任何需要特定权限才能执行的操作都可以在这个提升了权限的子进程中顺利进行。
85.shell的控制操作符:
这些操作符由shell进行解释和执行,类似于shell语言的语法元素,就像编程语言中的运算符和标点符号一样。常用的shell特殊字符:
>,>>,||,&&,#等
86.sudo+shell内部命令时,shell内部命令是怎么提升命令权限的?
由于内建命令不是独立的可执行程序,也就是说执行内部命令,不会新建进程(有时可以?)。
可以通过启动一个新的shell实例并以提升的权限执行这个shell,来间接地以root权限运行内建命令。例如:sudo bash -c '内建命令'
87.shell的控制操作符的使用注意
1.sudo echo "EmbedCharDev test" > /dev/chrdev和
2.sudo sh -c "echo 'EmbedCharDev test' > /dev/chrdev"的区别
1方法中:
当你执行一个带有重定向的命令时,shell首先解析整个命令行,识别出命令和重定向操作符。
对于重定向(>),shell会尝试打开指定的文件(在这个例子中是/dev/chrdev)以供写入。这一步是在当前用户权限下进行的,因为它是在解析命令行和准备执行命令之前由当前shell进程处理的。
当到达sudo部分时,sudo仅提升并应用于它直接调用的命令(这里是echo "EmbedCharDev test"),而不包括shell解析的其他部分,如重定向操作。
因此,如果/dev/chrdev文件的权限设置不允许当前用户写入,那么即使echo命令可以以root权限执行,重定向操作也会因为权限不足而失败。这是因为执行重定向的是当前的用户shell,而不是sudo创建的任何新shell或进程。
要使重定向操作也以提升的权限执行,需要确保整个命令行(包括重定向部分)都在提升权限的环境中执行,通常通过将整个命令行作为一个字符串传递给sudo sh -c来实现,如前所述。在这种情况下,包括重定向在内的整个命令行都在一个新的、以root权限运行的shell进程中执行。所以,方法1可能会执行失败,需要使用方法2
88.在比较高级的cpu中,代码在
运行时需要被加载到RAM(随机访问存储器)中,所以说
__init 宏确保这个函数只在模块加载时执行,之后会被内核丢弃以节省内存ram
89.在一些嵌入式系统中,特别是那些基于单片机(如ARM Cortex-M系列)的系统,
代码有时直接从非易失性存储(如Flash)执行,这种机制称为XIP(Execute In Place)。嵌入式设备中的Flash通常位于与CPU紧密相连的内部总线上,因此可以直接执行其中的代码,而不需要先将代码加载到RAM中。这是一种节省RAM资源的策略,适用于RAM资源非常有限的系统。
90.led_cdev.owner =
THIS_MODULE;设置设备的拥有者为当前模块,确保模块引用计数正确。这个怎么理解?模块拥有者是什么?
模块引用计数的作用
当一个模块被加载到内核时,它的引用计数设置为1。如果此模块被另一个模块使用,其引用计数会增加。相应地,当停止使用该模块时,其引用计数会减少。当引用计数达到0时,意味着没有任何模块或内核组件正在使用该模块,它可以被安全卸载。通过将owner设置为THIS_MODULE,你确保了只要相关的设备文件被使用,模块就不会被卸载。
将owner设置为NULL是不推荐的做法,因为这样可能会在设备使用中尝试卸载模块,从而导致不稳定或崩溃。因此,正确的做法是总是将owner设置为THIS_MODULE,除非有充分的理由相信在设备的整个使用期间,模块绝对不会被卸载。
总之,owner字段指定了某个内核对象(如字符设备)的“拥有者”模块。这个概念和字段的设定主要用于模块引用计数管理,确保在使用该内核对象时,其拥有者模块不会被意外卸载。
91.sysfs 文件系统用于把内核的设备驱动导出到用户空间,用户便可通过访问 sys 目录及其下的文件,来查看甚至控制内核的一些驱动设备,这句话怎么理解?
什么是sysfs 文件系统?
首先,sysfs 文件系统是linux中的其中一个虚拟文件系统,并且是
专用于控制sys路径下内核驱动的文件系统。她不存储在磁盘上,而是在内存中动态生成,它直接反映了内核中的设备模型。
sysfs文件系统是Linux操作系统中一个虚拟的文件系统,它提供了一个桥梁,通过这个桥梁用户空间的程序可以访问和控制内核空间的设备及驱动信息。这个文
件系统通常挂载在/sys目录下。sysfs文件系统的出现主要是为了提供一种结构化的方式来展现内核对象(如设备、驱动程序等)的信息,使得用户和程序能够以文件系统的方式访问这些信息,从而简化了对硬件的管理和控制。
sysfs提供了一种机制,允许用户空间的应用程序以一种安全和受控的方式访问内核空间的信息。
sysfs将硬件设备和驱动的信息以文件和目录的形式呈现。这样,用户和程序就可以使用标准的文件操作API(如open、read、write等)来查询或修改这些信息,而不需要编写复杂的设备驱动接口代码。
总的来说,sysfs文件系统简化了用户空间程序与硬件设备之间的交互,使得用户可以更容易地获取硬件信息和控制硬件设备,同时也为硬件设备的动态管理提供了支持。
/dev目录通常使用devtmpfs和udev来管理。
92.向总线中添加一个设备时,会进行匹配,这里的匹配什么意思?
设备的匹配:这是指操作系统中的一个过程,其中系统会为新检测到的设备寻找合适的驱动程序。匹配过程涉及到检查设备的标识信息(如制造商ID、产品ID等),并与系统已知的驱动列表进行比较,以找到能够支持该设备的驱动程序。
93.设备驱动模型里的设备、驱动、总线的属性文件是做什么用的?
查看信息:属性文件允许用户空间程序查看有关设备、驱动和总线的信息。
配置设备:通过写入属性文件,用户空间的程序可以修改设备的配置或行为。
状态反馈:属性文件可以用来向用户空间反馈设备的当前状态
触发操作:某些属性文件不是用来表示数据,而是用来触发设备或驱动程序中的操作。
94.通过接口,来注册和注销一个设备属性文件。我们可以
通过设备属性文件的接口直接在用户层进行查询/修改,避免了重新编译内核的麻烦。
这是因为这些属性文件背后是由相应的驱动程序来处理文件读写操作的,驱动程序根据写入属性文件中的值来改变硬件的状态或配置。(如,读取驱动属性文件的值,驱动来开关灯)
95.拿一个iic的设备举例,iic的读写操作就算是驱动,不同设备的iic地址就相当于私有资源也就是设备信息,多个iic设备都挂载在iic总线上,使用相同的iic驱动,但是每个设备的设备信息是私有并且不一样的,iic设备总线就去调用设备的私有信息再加上iic驱动来控制设备
96.sys目录下看到的设备文件和设备驱动文件是属于
attribute属性文件
Linux系统中,/sys 目录是sysfs文件系统的挂载点,/sys 目录包含的设备文件和设备驱动文件通常代表着设备和驱动的属性(attribute),这些文件通常是文本文件,可以读取来获取信息,有些还可以写入以更改设备或驱动程序的行为。
例如,你可以通过读写/sys目录下与网络接口相关的文件来启用或禁用网络接口,或者通过修改/sys目录下的文件来更改CPU频率等。
97./dev目录和/sys目录下的设备文件有着本质的区别
/dev目录包含的是设备文件,这些文件代表了系统中的硬件设备。这些设备文件允许用户空间的程序通过标准的输入输出系统调用来与硬件设备进行交互。(如,控制外设的输入输出)
/sys目录是sysfs文件系统的挂载点,它提供了一种机制,允许用户空间的程序查询和修改内核中的设备属性和配置。/sys目录下的文件通常不用于直接与设备进行数据传输,而是用来获取设备的信息(例如,设备类型、设备状态、配置参数等)或者修改设备的设置(例如,启用或禁用某个设备功能)。
98./dev目录通常是由udev管理的,它包含了代表系统中硬件设备的设备文件。udev是用户空间的设备管理器,负责监听内核发出的设备事件,如添加或移除设备,并动态地创建或删除/dev目录下的设备节点(文件)。这些设备文件为用户提供了访问和控制硬件设备的接口。udev不是一个真正的文件系统,而是一个动态设备管理系统,它运行在用户空间,并根据硬件设备的变化动态地创建和管理/dev目录下的设备节点。
99.sysfs是一个内核驱动的文件系统,专门用于
导出内核对象的信息给用户空间,允许用户空间的应用程序通过文件系统接口查询和修改内核对象的属性。sysfs以层次结构的方式组织,展现了设备、驱动程序和内核模块之间的关系。sysfs为每个内核对象创建了一个目录,目录内的文件代表该对象的属性。
主要区别
目的和功能:/dev主要用于提供访问和控制硬件设备的接口,而/sys(sysfs)用于展示和修改内核对象及其属性,提供了一种结构化的方式来理解和控制系统的硬件组件。
管理方式:/dev由udev(用户空间的设备管理器)动态管理,而/sys是由内核直接管理的文件系统。
内容和结构:/dev包含设备文件,这些文件代表系统中的硬件设备;/sys则以文件和目录的形式展现了内核对象和属性,其结构反映了设备、驱动和内核子系统之间的层次关系。
100.用户空间可能使用多种接口,如
ext2/ext3/ext4:这是Linux中最常用的文件系统之一。ext3是ext2的升级版,增加了日志功能,而ext4是进一步的改进,支持更大的文件和卷大小,减少碎片,提高性能。
xfs:这是一种高性能的文件系统,适用于大型文件处理和数据库应用。xfs支持大容量存储和高效的数据访问。
btrfs:这是一种较新的文件系统,支持高级功能,如快照、动态扩展、多卷支持等,适用于存储管理和数据恢复。
101.设备的操作方法结构体是单独的,和设备结构体是分开的
102.一个设备初始化流程
一个设备就是一个设备结构体,创建完设备结构体后,这个设备就存在了,但是系统不认识这个设备,这时候就要使用
alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);这个函数将用设备名字申请主设备号,并且将可使用的从设备号以及申请到的主设备号放到第一个参数devno中。
接着cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + i);使用申请到的可用主设备号和次设备号结合得到一个可用设备号赋值。
cdev_add(&led_cdev[i].dev, cur_dev, 1);将cur_dev设备号付给led_cdev[i].dev这个设备,自此,系统就认识这个设备了!!!
led_chrdev_class = class_create(THIS_MODULE, "led_chrdev");
为本模块创建一个设备类
device_create(led_chrdev_class, NULL, cur_dev, NULL,DEV_NAME "%d", i);
将设备号与新建的设备类关联起来,告诉系统,这个编号的设备属于这个设备类,
并且为这个设备创建一个设备节点(为了给用户空间使用)
cdev_init(&led_cdev[i].dev, &led_chrdev_fops);
为设备关联操作结构体
led_cdev[i].dev.owner = THIS_MODULE;
设置这个设备属于哪个内核模块
103.class_create函数是用于创建一个设备类的内核API。
设备类(class)是一个高级别的概念,用于将具有相似功能的设备组织在一起。一个设备类代表了一类设备的抽象概念。例如,所有的字符设备可能属于一个类,所有的网络设备可能属于另一个类。
struct class *class_create(struct module *owner, const char *name);
owner: 指向一个模块对象的指针,通常使用THIS_MODULE宏。这个参数用于指定哪个模块拥有这个新创建的类。在模块被卸载时,能够确保所有使用该模块的资源都已经被正确地释放。使用THIS_MODULE宏可以确保当该驱动模块被卸载时,与之相关的类也会被自动销毁。
name: 类的名称,这个名称是在/sys/class目录下创建的目录名。例如,如果名称为"led_chrdev",那么将在/sys/class/下创建一个名为led_chrdev的目录。这个名称对于用户空间的程序来说是可见的,并且通常用于识别设备类型。
例如,led_chrdev_class = class_create(THIS_MODULE, "led_chrdev");这句的作用是创建一个名为"led_chrdev"的设备类,并将这个类与当前的模块(即编写这段代码的驱动模块)关联起来。这样,当加载这个模块时,就会在/sys/class/目录下看到一个名为"led_chrdev"的目录,这有助于用户空间的程序识别和管理这类设备。
104.struct led_chrdev {
struct cdev dev;//字符设备
unsigned int __iomem *va_dr;
unsigned int __iomem *va_gdir;
unsigned int __iomem *va_iomuxc_mux;
unsigned int __iomem *va_ccm_ccgrx;
unsigned int __iomem *va_iomux_pad;
}
这样的方式可以
将一个字符设备扩充,
通过定义其他的成员变量(如unsigned int __iomem *va_dr等),你在结构体中包含了与LED相关的其他信息,如寄存器的虚拟地址、物理地址和其他必要的硬件信息。这些成员变量使得在驱动程序中可以轻松访问和控制LED硬件。
因此,创建这样的结构体确实是在扩充字符设备结构体的功能,使其不仅能表现为一个标准的字符设备,还能满足特定硬件(如LED)的操作需求。
105.
<>括号内主要填写的是整数值,这些整数可以是直接写出的数值、引用其他节点的地址或大小、或者是预定义的枚举值等。这些整数通常用于表示硬件资源的参数,如内存地址、大小、中断号、标志等。设备树中不使用字符串或变量名(以编程语言中理解的方式)填充<>。
106.pinctrl子系统和gpio子系统配置的不同功能的区别
聚焦点:pinctrl子系统则更加关注引脚的多功能性和电气属性的配置。它管理的是如何设置引脚的多路复用(即一个物理引脚可以被配置为不同的功能,比如GPIO、I2C、SPI等),以及引脚的电气特性(如上拉/下拉电阻、驱动强度等)。
作用:通过pinctrl子系统的配置,内核可以在需要时切换引脚的功能,以适应不同的硬件需求。这是对硬件资源的一种更底层的管理,确保引脚能够按照期望的方式工作。
不同的配置目的:gpio子系统的配置目的是使内核能够使用GPIO控制器和引脚,pinctrl子系统的配置目的是确保引脚能够以正确的模式和电气特性工作。
灵活性和复用性:分开配置允许更灵活地管理硬件资源。例如,同一个物理引脚可能在不同的时间被用于不同的功能,这需要pinctrl子系统来管理。而gpio子系统则提供了一个统一的接口用于控制这个引脚(当它被配置为GPIO时)的高低电平状态。
简而言之,尽管两者都涉及寄存器信息,但由于它们关注的方面和目的不同,因此在设备树中需要分开配置,以确保内核能够正确地初始化和管理这些硬件资源。
107.&iomuxc节点通常是一个顶级节点,用于定义和控制SoC(System on Chip)内部的I/O复用(IOMUX)和引脚控制(pin control)。它通常包含了多个子节点,每个子节点代表了一组特定的引脚配置。这些子节点可以被其他设备节点引用,以便为这些设备设置引脚功能和属性。
pinctrl-0属性是用于指定设备在其默认状态下应该使用哪组引脚控制配置。每个设备节点可以有自己的pinctrl-0,来引用&iomuxc下定义的某个特定的引脚控制组。
对于&iomuxc节点本身而言,它不直接使用pinctrl-0这样的属性,因为它作为一个容器,其作用是定义可供引用的引脚控制组,而不是去直接使用这些引脚控制组,
所以&iomuxc节点中的pinctrl-0值没有什么意义。
108.当一个平台设备通过platform_device_register函数(或相关函数)注册到内核时,该设备的信息会被添加到platform_bus_type(
系统中设备平台总线就一个)所管理的设备列表中。内核的设备模型代码会自动尝试为这个新注册的设备找到一个
匹配的驱动。这个“尝试匹配”的过程,本质上是内核遍历platform_bus_type管理的所有已注册的平台驱动,并检查它们是否与新设备匹配的过程。
设备树中的设备通过兼容性属性(compatible属性)来指示哪个驱动支持该设备,这进一步简化了设备和驱动的匹配过程。
109.驱动使用内核模块,使用内核编译好内核模块并且使用insmod或modprobe命令挂载内核模块后,执行内核模块init函数,里面进行平台驱动注册到内核,系统根据驱动的类型将驱动放到不同的总线上,接着当我们再注册设备到内核后,相应总线(根据设备类型)为设备匹配该总线上的驱动,如果匹配上,就执行probe函数
110.在调用 func_init 后,/proc/modules中会 新建一个以模块名为名的目录
111.内核模块的作用在于提供一种机制,允许在Linux内核运行时动态地加载和卸载代码。这些模块可以在不需要重启系统的情况下,增加或改变内核的功能。
112.
设备注册内核函数的调用位置的不同方法1.使用内核模块2.使用设备设备树节点后,将设备驱动内核模块注册内核后执行的proe函数中设备注册内核,两者的区别
1.
使用内核模块进行设备注册
优点:
动态加载和卸载:允许根据需要添加或移除驱动程序,不需要重启系统。
开发和测试方便:开发者可以快速迭代驱动程序的开发和测试过程。
缺点:
需要显式管理:需要管理员手动加载和卸载模块,或者通过udev规则自动化这一过程。
依赖模块依赖关系管理:模块间可能存在依赖关系,需要确保正确的加载顺序。
2.
使用设备树节点进行设备注册
优点:
自动设备注册:系统启动时自动识别和注册设备,简化了设备管理。
硬件抽象:设备树为硬件提供了一个标准化的描述,使得内核代码更加通用,不依赖于特定的硬件。
缺点:
修改和更新复杂:更新设备树可能需要重新编译或修改启动配置,这对于最终用户来说可能较为复杂。
113.在Linux内核中,I2C控制器是一个具体的iic外设,每个控制器管理一个或多个I2C总线。,由于一个芯片上有多个iic外设,所以
有多个iic总线。
114.
多个I2C总线的理由:
1. 硬件反映
如果硬件平台上存在多个I2C控制器,每个控制器管理不同的物理I2C总线,那么在软件层面就需要有对应的抽象来管理这些总线。这样做可以确保软件能够正确地与每个物理I2C总线上的设备进行通信。
2. 设备隔离
在复杂的系统中,不同的设备或设备组可能需要被隔离在不同的I2C总线上,以避免干扰、满足不同的性能需求或解决电气兼容性问题。例如,高速设备和低速设备可能需要分别放在不同的I2C总线上以优化性能和信号完整性。
3. 扩展性和灵活性
多个I2C总线可以提供更好的系统扩展性和灵活性。随着系统中的I2C设备数量增加,可能会超出单个总线的承载能力。在这种情况下,有多个I2C总线可以分散通信负载,防止总线拥塞,并允许更多的设备被同时连接和控制。
4. 并行操作
使用多个I2C总线可以实现并行操作,这样不同的总线可以同时进行数据传输,从而提高总体的系统性能和响应速度。
5. 逻辑分组和管理
在软件层面,将设备根据功能、性能需求或物理位置分组到不同的I2C总线上,可以简化设备管理和驱动程序的实现。例如,一个I2C总线专门用于传感器数据采集,而另一个用于用户界面设备,可以使系统设计更加模块化和清晰。
115.I2C控制器通常指的是芯片上负责管理I2C总线通信的
硬件部分,它包括一系列外设寄存器和逻辑电路。这些寄存器用于配置I2C控制器的工作模式、管理数据传输、控制总线状态等。
116.I2C适配器(i2c_adapter)的抽象成I2C控制器?
I2C适配器的抽象是指在软件层面对I2C控制器及其管理的I2C总线进行的封装和表示,使得软件开发者可以不用关注具体硬件细节就能与I2C设备进行交互。这种抽象通过在操作系统中定义一个通用的适配器接口来实现,这个接口提供了与I2C设备进行通信所需的一系列操作和方法。
117.编译内核使用的两条命令讲解:
1 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- npi_v7_defconfig
2 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
第1条命令用于为特定的硬件配置生成默认的内核配置文件
make:是用来编译和构建程序的工具。
ARCH=arm:指定目标架构为ARM。这告诉编译系统我们正在为ARM架构构建内核。
CROSS_COMPILE=arm-linux-gnueabihf-:设置交叉编译工具链的前缀。这意味着我们将使用一个为ARM架构构建的,能够在非ARM系统(如x86架构的PC)上运行的编译器。arm-linux-gnueabihf-前缀用于找到正确的工具链程序,如编译器(gcc)、链接器(ld)等。
npi_v7_defconfig:这是一个针对特定硬件平台(在这个例子中可能是一个名为“npi_v7”的平台)的默认
配置文件。执行这个命令会创建一个基于这个默认配置的.config文件,这个文件包含了内核的配置选项。这一步是编译过程的准备步骤,确保内核配置匹配特定的硬件需求。
第二条命令用于构建设备树二进制文件(DTB)。它依据前一步生成的内核配置来编译设备树源文件(DTS)。
dtbs:这是告诉make构建所有可用的设备树二进制文件(DTB)。这一步是根据内核配置和设备树源文件(DTS)来生成.dtb文件,这些文件描述了硬件的布局和配置,供内核在启动时使用。
118.对于大多数硬件系统来说,确实通常只有一个特定的设备树源文件(DTS文件)被编译成设备树二进制文件(DTB),用于描述和配置该硬件系统。
119.系统在启动的时候,会为设备树的compatible属性创建一个compatible列表
当内核解析设备树时,它会为每个节点创建一个设备模型实例。
高效的匹配过程:内核并不需要遍历设备树中的所有节点来查找匹配。相反,内核在解析设备树并创建设备实例时,就已经知道了每个设备的compatible属性。当驱动程序注册到平台总线时,内核会查找具有匹配compatible属性的设备实例,并尝试将这些设备与驱动程序匹配。
120.当一个驱动内核模块注册到相应总线后,该总线去
搜索该总线下所有的设备以及
设备树的compatible列表,虽然可能设备树节点可能有很多,但是搜索列表是很高速的。
121.在gpio的pinctrl章节中,字符设备节点.owner赋值为THIS_MODULE,感觉字符设备节点好像就是提供接口给用户的,和驱动模块没什么关系,为什么一定要使用THIS_MODULE?
首先,字符设备的接口函数read,write是定义在驱动模块的c文件中的,一但驱动模块卸载,整个c文件里的内容
全部从内存消失,所以,将字符设备节点赋值THIS_MODULE,好处是:你正在明确告诉内核:“这个设备是由当前模块控制的”。这意味着只要有应用程序正在使用这个设备(即设备文件被打开),内核就不会允许卸载这个模块。这是一种安全机制,确保了当设备正在被使用时,其背后的驱动代码始终可用和稳定。
122.添加设备至cdev_map散列表中,这个怎么理解?
cdev_map是一个散列表(哈希表)cdev_map散列表的主要作用是提供一种高效的方式来管理和访问系统中的字符设备,特别是当系统中有大量的字符设备时。通过设备号作为键来快速定位到具体的字符设备
当用户空间程序尝试打开/dev/myled设备文件时,内核就能通过cdev_map散列表,使用你提供的设备号找到对应的cdev实例,然后根据该实例的file_operations处理相应的操作请求。
123.节点的reg怎么使用的?比如
1 iomuxc: iomuxc@20e0000 {
2 compatible = "fsl,imx6ul-iomuxc";
3 reg = <0x20e0000 0x4000>;
4 };
reg:表示的是引脚配置寄存器的基地址?
为什么是寄存器的基地址?是谁决定的?
设备树的规范和用法是由设备树规范文档定义的,而设备树中各个节点属性的使用,包括reg属性的含义,也是由这份规范以及相关的硬件架构文档决定的。开发者在创建或修改设备树文件时,需要遵循这些规则和文档,以确保硬件被正确配置和使用。
124.设备树的设计初衷是为了提供一种硬件描述方法,让操作系统能够在不需要更改内核代码的情况下支持多种硬件。这意味着,硬件的任何配置信息,包括引脚复用,都应该通过设备树来描述。
125.compatible属性的作用
内核通过设备树中节点的compatible属性来匹配设备和它的驱动程序。在你的例子中,compatible = "fire,rgb-led";告诉内核,这个节点应该由处理fire,rgb-led兼容性的驱动来管理。一旦设备与驱动匹配,驱动程序就会根据其编码来读取和解释节点中定义的属性。这意味着,对于自定义的属性(如rgb_led_red),必须在驱动程序中明确地编写代码来查询这些属性的值,并据此来配置硬件或执行相关的操作。
126.设备树中定义官方和自定义属性:
官方属性:设备树规范和特定硬件平台(如ARM、x86)定义了一系列标准的属性名和节点类型,这些是被Linux内核和各硬件平台广泛认可的。如compatible、reg、interrupts等,这些属性有固定的含义和解析方式。
自定义属性:对于不被设备树规范直接定义的属性,如你的rgb_led_red,内核本身不会预先知道它们的存在或含义。这些属性的意义和处理逻辑需要在相应的设备驱动程序中实现。开发者需要确保驱动程序能够正确地识别和使用这些自定义属性。
127.每个芯片厂商的 pinctrl 子节点的编写格式并不相同,这不属于设备树的规范
所以要遵循芯片要求编写pinctrl 子节点,也就是iomuxc节点内的内容
如:就是要求按照这个框架写
1 pinctrl_ 自定义名字: 自定义名字 {
2 fsl,pins = <
3 引脚复用宏定义 PAD(引脚)属性
4 引脚复用宏定义 PAD(引脚)属性
5 >;
6 };
128.为什么对于总线来说,描述信息和驱动是在一起的,为什么不像设备一样,有专门的描述结构体和驱动结构体?????
Linux内核中的这种设计是为了实现高效的管理和灵活的扩展。将总线的描述信息和驱动函数放在一起,使得总线能够作为一个统一的实体来管理连接的设备和驱动,这样既简化了设计,也提高了效率。将设备和设备驱动分开是因为一个驱动可能对于多个设备,而一个总线就对应一个驱动
129.button_GPIO_number =
of_get_named_gpio(button_device_node, "button_gpio", 0);
内核提供的函数,用于从设备树的某个节点中获取一个其中的命名了的GPIO的引脚编号。这个编号是全局唯一的,在Linux内核中,每个GPIO都有一个全局唯一的标识号
130.error =
gpio_request(button_GPIO_number, "button_gpio");
GPIO(通用输入输出)引脚在多任务和多模块的操作系统中是共享资源。就像一个公共的会议室需要通过预定来避免冲突一样,操作系统中的GPIO引脚也需要一种机制来管理谁可以使用哪个引脚,以及何时使用它。这就是gpio_request函数的角色。
想象一下,你开发了一个内核模块,它需要通过一个GPIO引脚来读取一个外部传感器的状态。系统中可能还有其他模块或驱动,它们也可能想要使用同一个GPIO引脚来进行完全不同的任务,比如控制一个LED指示灯。
如果两个模块都不加约束地尝试配置和使用这个GPIO引脚,就会发生冲突。例如:
一个模块将其配置为输入,另一个模块将其配置为输出。
同时尝试读写该引脚,导致预期之外的行为。
这种冲突可能导致硬件行为异常,甚至损坏,而且调试此类问题非常困难。
gpio_request的作用
gpio_request函数允许内核追踪哪个模块请求了某个GPIO引脚。当你调用这个函数并为其提供一个GPIO编号和一个标签时,内核会记录下这个请求。如果另一个模块尝试请求同一个GPIO引脚,内核会拒绝这个请求,并可能打印一条消息说明这个引脚已经被占用。
button_gpio"的用途
标识符:它作为一个标识符,帮助开发者在查看系统日志或者使用GPIO相关的调试工具时,快速识别出这个GPIO引脚是被哪个模块或用途占用的。
调试和错误追踪:如果有多个地方错误地请求了同一个GPIO,或者一个GPIO没有被正确释放就被再次请求,这个标签可以帮助快速定位问题。
自定义性
"button_gpio"这个字符串是自定义的。你可以根据实际情况为它赋予一个有意义的名字
131.interrupt_number =
irq_of_parse_and_map(button_device_node, 0);
为之前申请的button_device_node设备树节点内的索引为0,也就是第一个interrupts 属性分配系统唯一中断号到返回值
132.request_irq(interrupt_number,button_irq_hander,IRQF_TRIGGER_RISING,"button_interrupt",device_button);
中的"button_interrupt"是自定义的
133.我的开发板上正在跑linux,也就是说这个系统里面是有相应架构的内核源代码的吧?
并不是所有运行Linux的系统都会自带内核源代码。运行中的系统通常包含编译后的内核(一个或多个二进制文件),而内核的源代码通常用于开发目的,需要时必须单独下载。
134.
设备树以及设备树插件,挂载在
/proc/device-tree/
设备挂载在
/dev
总线挂载在
/sys/bus/
135 gcc -o ctest ctest.c -I . 的解释
-o ctest: 指定输出文件名为 ctest。如果没有这个选项,
默认输出文件名为 a.out。
-I .: 指定包含路径(include path),即编译器在当前目录(.)下搜索头文件。
136GCC 及大多数命令行工具都是以空格来分割命令行参数的,例如:
gcc -o output file1.c file2.c
output:这是紧跟在 -o 后面的参数,表示输出文件的名称。不会认为file1.c file2.c
也是输出
注意-o只是代表输出文件的名字,没有特定要求gcc编译成什么文件,所以就用默认gcc编辑方式,即直接生成可执行文件
-I .:这个选项指定了包含文件(头文件)的搜索路径。.表示当前目录,所以编译器会在当前目录下查找包含文件
137,makefile检查是否需要更新
## makefile
targeta: targb
gcc ctest.c config.c -o targeta -I .
pwd
targb:
ls
这段makefile中,targeta的依赖是目录,不是文件,没有办法去检测是不是最新,所以执行make就一定执行targb和targeta
如果改成targeta: ctest.c config.c
就会检查 ctest.c config.c
有没有更新
138.c文件变成可执行文件的流程
1.预编译c->i
将宏替换,头文件内容引用到c文件
2.编译i->s(汇编代码)
编译阶段作用是什么?是检查语法的?
编译阶段的主要作用之一是
检查语法,还包括词法分析、语法分析、语义分析、中间代码生成和优化、目标代码生成和优化等多个步骤。然后生成汇编代码(不检查函数是否定义,不定义也不报错)
3.汇编s->o(目标文件)
将汇编代码生成机器码
4.链接o->bin(可执行文件,一般是bin,不同系统有可能是其他格式)
将生成的所有目标文件进行链接成一个可执行文件
139.makefile中抑制回显
加@就抑制回显,不加@默认会有回显
targ1:
@echo "aaaa"
@ls
140.echo "$(shell pwd)"的理解
$(...) 语法
$(...) 是 Makefile 中用于引用
变量或函数调用的语法。它可以用来执行 Makefile 的内置函数或引用变量。
$(shell ...) 是一个内置函数,用于执行由括号内的命令并返回其输出。
(shell ...) 函数
shell 是 Makefile 的内置函数之一。(还有很多其他的高级内置函数,用于数据处理)
$(shell pwd) 的意思是执行 pwd 命令,并将其输出作为函数的返回值。
注意:在 $(shell pwd) 中,pwd 是传递给 Makefile 的 shell 内置函数的参数。$(shell ...) 内置函数会执行括号内的 shell 命令,并将该命令的输出作为字符串返回。
141echo"1"和echo1有什么区别?"1"是给echo传递的是ascall码的1吧?
在 echo 命令中,echo "1" 和 echo 1 的区别并
不在于传递的是 ASCII 码还是字符串,因为 echo 命令的作用是输出其参数,它会将所有参数视为字符串进行处理。即使传递的是数字,echo 也会将其视为字符串输出。因此,这两者在大多数情况下输出是相同的
引号的作用
引号在 shell 中的作用主要是用于处理包含空格或特殊字符的字符串,并确保这些字符串被正确解析和传递。
142makefile中一行是一个命令
cc = gcc
all: aa
$(cc) -o aa bb.c
在makefile中,这样使用是可以的,说明makefile中命令的解析和执行是基于行和字符的
所以:
targ1:
echo "1111" gcc -o
echo "1111" gcc -o 这一行会被视为一个完整的 shell 命令。而不是echo命令和gcc命令
143Makefile 的命令部分实际上是 shell 脚本的一部分
在 Makefile 中,每一行命令都会被认为是一个完整的 shell 命令,并传递给 shell 进行解析和执行。Makefile 的命令部分实际上是 shell 脚本的一部分。因此,每一行命令都是由 shell 来解释和执行的。
144makefile命令解析和执行流程:
定义目标和依赖:Makefile 首先解析目标和依赖关系。
执行命令:在目标的依赖项满足后,Makefile 会依次执行该目标下的命令。每一行命令会作为一个独立的 shell 命令来执行。
145gcc -c -o hello_main.o hello_main.c -I.由于-c在前面,所以和前面的hello_main.c配对,-o在后面,和后面的hello_main.o配对,这样按照顺序配对的?错误!!!
编译器不按这种配对顺序工作。编译器解析命令行参数时,-o 后面紧跟的参数总是指定输出文件的名称,而其他参数则根据其上下文来解释。
-o hello_main.o:指定输出文件名为 hello_main.o -o配对和他紧挨着的,而不是按照顺序
146%.o: %.c
$(bb) -c %< -o %.@ 的理解
%.o:匹配任何以 .o 结尾的目标文件。
%.c:匹配任何以 .c 结尾的源文件,且模式中的 % 表示
相同的前缀!!!!!!。
147在Makefile中,如果目标的依赖项不存在,make会报错并终止执行。这是因为make需要这些依赖项来正确地生成目标文件。
148.显示规则和模式规则的区别
显式规则:直接指定目标文件和依赖文件。用于定义
单个目标的生成方法。
模式规则:使用通配符(通常是 %)来匹配一组目标文件和依赖文件,适用于
多个目标文件的生成。
显式规则中没有通配符,目标和依赖文件都是明确指定的。
模式规则中使用 % 作为通配符,匹配文件名的一部分。
ctest.o: ctest.c config.h
gcc -c ctest.c -o ctest.o -I .
这是一个显式规则(explicit rule)。显式规则明确地指定了特定目标文件(ctest.o)以及它的依赖文件(ctest.c 和 config.h),并且定义了生成该目标文件的具体命令。
%.o: %.c
gcc -c $< -o $@ -I .
这是模式规则,使用通配符(通常是 %)来匹配一组目标文件和依赖文件,适用于多个目标文件的生成。
149显式规则和模式规则不能直接混用
config.o: config.c %.h
gcc -c config.c -o config.o -I .
这个规则是无效的,因为 config.c 是显式指定的文件,而 %.h 是一个模式通配符。这种混用是不允许的。
显式规则和模式规则不能直接混用。每种规则有自己的语法和用途,不能在一个规则中同时使用两种不同的模式。