1. 进程和线程的概念、区别以及什么时候用线程、什么时候用进程?
(1)线程
线程是CPU任务调度的最小单元、是一个轻量级的进程
(2)进程
进程是操作系统资源分配的最小单元
进程是一个程序动态执行的过程,包括创建、调度和消亡
(3)进程和线程的区别
1. 内存空间(安全性:进程>线程)
(1)进程:每个进程有独立的地址空间、一个进程的崩溃不会影响其他进程
(2)线程:同一进程中的线程共享相同的地址空间,因此一个线程的崩溃可能导致整个进程的崩溃
2. 通信方式(通信:线程比进程容易)
(1)进程:进程间通信相对复杂,比如使用管道、消息队列、共享内存等
(2)线程:线程间由于共享内存,通信相对简单,可以直接通过全局变量、线程邮箱等通信
3. 创建开销
(1)进程:创建进程开销大,因为需要分配独立内存空间
(2)线程:创建线程开销小,因为线程共享进程的资源,不需要重新分配内存
4. 调度和切换
(1)进程:进程切换需要切换整个内存地址空间,开销较大
(2)线程:线程切换只需要保存和恢复少量的寄存器状态,开销较小
5. 资源独立性(线程会发生资源竞争)
(1)进程:各进程资源独立,不会相互影响
(2)线程:线程共享资源,可能会发生资源竞争,需要考虑同步问题
(4)进程和线程的适用场景
1. 进程
(1)隔离需求高
- 当不同的任务需要完全隔离,以避免互相影响时,使用进程是更好的选择。因为每个进程有独立的内存空间,即使一个进程崩溃,也不会影响到其他进程。
- 示例:网页浏览器使用多进程来隔离不同的标签页,一个标签页崩溃不会影响整个浏览器或其他标签页。
(2)多核心利用
- 在多核心系统上,进程可以有效地利用多个核心,适合进行大规模的并行计算任务。
- 示例:大型数据处理任务,如视频编码、科学计算等。
(3)安全性
- 当需要提高安全性,防止不同任务之间共享数据或资源时,进程是更好的选择。因为进程间通信需要使用专门的机制,如管道、消息队列等,这增加了安全性。
- 示例:需要处理敏感数据的金融或医疗应用程序。
(4)需要稳定性
- 如果应用需要很高的稳定性,使用进程可以防止一个模块的错误影响到整个系统。
- 示例:操作系统中的系统服务通常运行在独立的进程中,以确保稳定性。
2. 线程
(1)资源共享需求高
- 如果不同任务需要频繁地共享数据或资源,线程是更好的选择,因为同一进程内的线程可以直接访问共享内存。
- 示例:多线程Web服务器,各线程共享同一个缓存池和数据库连接池。
(2) 轻量级并发
- 线程创建和切换的开销较小,适合处理大量轻量级并发任务。
- 示例:实时聊天应用中的消息处理,游戏开发中的多任务处理。
(3)响应性要求高
- 在需要快速响应用户请求的应用中,使用线程可以避免长时间的计算任务阻塞用户界面。
- 示例:图形用户界面(GUI)应用程序,后台线程处理数据,主线程响应用户操作。
(4)I/O密集型任务
- 线程可以用于处理I/O密集型任务,因为它们可以在等待I/O操作时不阻塞整个进程。
- 示例:文件下载、网络请求处理。
选择进程还是线程的考虑因素
- 内存使用:进程有独立的内存空间,内存使用较多;线程共享内存,使用较少。
- 性能:进程间通信成本高,适合长时间运行的任务;线程之间通信快,但需要同步机制避免资源竞争。
- 开发难度:线程编程复杂,需要处理同步和死锁问题;进程则比较独立,但需要处理进程间通信。
2. TCP/IP分几层?每层的核心任务是什么?
(1)TCP/IP分四层:网络接口层、网络层、传输层、应用层
(2)每层核心任务
1. 网络接口层
负责在物理网络上发送和接收数据帧,处理与网络硬件设备的接口
处理数据链路层和物理层的功能,包括帧的封装和解封装、物理地址的处理以及实际的介质访问控制
2. 网络层
负责数据包的路由选择和转发,即如何将数据包从源地址发送到目的地址
3. 传输层
提供端到端的通信服务,包括建立、管理和终止传输连接,保证数据传输的可靠性和正确性
4. 应用层
提供应用程序之间的通信和数据交换,处理具体的应用程序协议,定义应用程序间如何交换数据
3. HTTP的端口号是什么?端口号的作用是什么?
(1)HTTP的默认端口号是80
(2)端口号的定义和作用
1. 定义:端口号是计算机网络中用于标识不同服务和应用程序的逻辑地址
2. 作用
(1)区分服务:在同一台主机上运行多个网络服务时,端口号用于区分不同的服务。例如,HTTP通常使用端口80,而HTTPS通常使用端口443。
(2)数据传输:当数据包到达一台主机时,端口号用于将数据包准确地传递给相应的应用程序或服务。这样,即使多种服务同时运行,也能确保数据被发送到正确的目的地。
(3)安全控制:通过开放或关闭特定端口,管理员可以控制网络访问,增强安全性。例如,可以通过防火墙设置来允许或禁止某些端口的通信。
4. http的作用?中文名是什么?主要用在什么地方?
(1)http的作用
1. 通信基础
http是客户端和服务器之间通信的基础协议,定义了客户端(如浏览器)与服务器之间如何请求和传输数据
2. 数据传输
http用于传输多种类型的数据,包括文本(HTML)、图像、音视频
(2)中文名:超文本传输协议
(3)用处
1. Web浏览
主要用于浏览器和Web服务器之间的数据传输。当用户输入网址并访问网页时,浏览器通过HTTP与服务器通信以获取网页内容
2. API通信
现代Web应用广泛使用HTTP作为API(应用程序编程接口)通信协议,以交换数据。例如,RESTful API通常使用HTTP协议
3. 文件传输
虽然专门的文件传输协议如FTP也很常见,但HTTP也经常用于下载和上传文件
4. 网络服务
如云计算服务、微服务架构等,使用HTTP进行服务间的通信
5. TCP(传输控制协议)和UDP(用户数据报协议)的区别
(1)连接性
1. TCP:面向连接的协议。在数据传输前,TCP需要在发送方和接收方之间建立一个连接,这个过程称为“三次握手”。连接建立后,数据可以可靠地传输,结束时通过“四次挥手”断开连接。
2. UDP:无连接的协议。UDP在发送数据前不需要建立连接,直接将数据发送给接收方,不保证数据的顺序和完整性。
(2)可靠性
1. TCP:提供可靠的数据传输。TCP通过序列号、确认应答、重传机制、流量控制和拥塞控制等技术,确保数据不丢失、不重复且按顺序到达
2. UDP:不提供可靠性保障。UDP不保证数据包能被正确接收,也不提供重传、排序、流量控制等机制,数据可能丢失、重复或乱序
(3)速度和效率
1. TCP:由于需要建立连接、确认数据、重传丢失的数据等,TCP的开销较大,数据传输速度相对较慢。
2. UDP:UDP协议头部开销小,传输速度快,因为它省去了连接建立和数据确认的步骤,更适合实时应用
(4)流量控制和拥塞控制
1. TCP:具有流量控制和拥塞控制机制。流量控制防止发送方过快发送数据导致接收方处理不过来,拥塞控制则避免网络拥堵
2. UDP:没有流量控制和拥塞控制机制,因此不会调整发送速度
(5)适用场景
1. TCP:适用于需要可靠性、数据完整性和顺序传输的应用
- 网页浏览:HTTP、HTTPS
- 文件传输:FTP
- 电子邮件:SMTP、IMAP、POP3
- 远程登录:SSH
2. UDP:适用于对速度要求高、容忍一定数据丢失的应用
- 视频直播和实时音频:如视频会议、IP电话(VoIP)
- 在线游戏:一些快速响应的网络游戏
- 广播和多播通信:如网络广播、局域网内的服务发现
(6)报文格式和长度
1. TCP:报文段包括源端口号、目标端口号、序列号、确认号、数据偏移、控制位、窗口大小、校验和、紧急指针、选项和填充等,头部较复杂且较长
2. UDP:数据报包含源端口号、目标端口号、长度和校验和,头部非常简洁,长度固定为8字节。
综上所述,TCP适合需要高可靠性和数据完整性传输的应用,而UDP则适用于需要快速传输且对数据丢失要求不高的应用。选择使用TCP或UDP取决于应用程序的具体需求和网络环境。
6. 三次握手的过程
三次握手是TCP(传输控制协议)连接建立的过程,用于确保客户端和服务器之间建立可靠的通信链路。这个过程由三个步骤组成,因此称为“三次握手”
(1)第一次握手(SYN)
- 客户端向服务器发送一个SYN(同步序列编号)报文段,表示客户端希望建立连接。这个报文段中包含一个初始的序列号(Sequence Number),例如
SEQ = x
。
(2)第二次握手(SYN-ACK)
服务器收到客户端的SYN报文段后,回应一个SYN-ACK报文段。这个报文段包括服务器的SYN序列号(例如SEQ = y
)以及对客户端SYN的确认号(Acknowledgment Number),即ACK = x + 1
。服务器用这个确认号来告诉客户端,它收到了客户端的SYN请求,并且正在等待客户端的响应
(3)第三次握手(ACK)
客户端收到服务器的SYN-ACK报文段后,发送一个ACK报文段给服务器,确认接收到服务器的SYN报文段。这个报文段中包含的确认号是ACK = y + 1
,表示客户端已经接收到了服务器的SYN报文段,并确认了它的序列号
完成以上三次握手后,TCP连接正式建立,双方可以开始数据传输。这个过程确保了通信的可靠性和双方的同步,避免了丢包或混乱的情况。
7. 线程什么时候互斥、什么时候同步
互斥(Mutual Exclusion)的目的是防止多个线程同时访问共享资源(如变量、文件、数据库等),从而避免竞争条件(Race Condition)的发生。当一个线程正在访问共享资源时,其他线程被阻止,直到该线程释放资源。这通常使用互斥锁(mutex)来实现。
(1)互斥
1. 修改共享数据:多个线程可能同时尝试修改共享数据,这时需要互斥来确保数据的完整性
2. 访问临界区:临界区是指一个需要被单独访问的代码块,多个线程不能同时执行这个代码块
3. 控制硬件资源:如打印机、网络连接等硬件资源,通常需要互斥机制来确保只有一个线程在某一时刻可以使用它
同步(Synchronization)的目的是协调线程的执行顺序,确保线程按照预期的顺序执行某些操作。与互斥不同,同步不仅限于共享资源的访问,还可以用于协调多个线程之间的活动。
(2)同步
1. 任务依赖性:一个线程的任务依赖于另一个线程的任务完成。例如,线程A必须等待线程B计算出结果后才能继续执行
2. 事件通知:一个线程需要等待另一个线程完成某个事件才能继续。例如,主线程等待子线程完成某个工作,然后汇总结果
3. 线程通信:线程之间通过某种机制(如条件变量、信号量等)进行通信,确保它们能够在适当的时间点进行协作
- 互斥主要用于避免多个线程同时访问共享资源,从而防止数据竞争和不一致。
- 同步主要用于协调线程执行顺序,确保线程之间的正确协作和通信。
8. 进程间为什么要通信?进程间通信的方式有几种?
(1)进程间通信的原因(作用)
1. 数据共享
当进程间需要共享数据时,需要进行通信。如客户端-服务器模型中的数据传输,或多个进程共同操作一个共享资源(例如数据库)
2. 资源分配和协调
多个进程可能需要协作来完成某项任务,通信机制可以帮助它们协调资源的使用和任务的调度。
3. 同步和控制
有些应用程序需要多个进程按照特定的顺序执行,进程间通信可以用于进程同步和控制
4. 模块化设计
现代软件开发中,应用程序通常被设计成多个独立的进程模块,这些模块通过通信来实现整体功能
5. 负载均衡和任务分配
在分布式系统中,进程间通信有助于将任务分配给不同的进程,以实现负载均衡
(2)进程间通信的方式
1. 管道
这是最古老和基本的IPC机制之一。管道允许两个进程通过一个单向数据流进行通信。通常有匿名管道和命名管道(FIFO)
2. 消息队列
消息队列是一种基于消息的通信机制,允许进程以消息为单位进行通信。消息可以被存储在队列中,供接收进程读取
3. 共享内存
共享内存是最快的IPC机制,因为它允许多个进程直接访问相同的内存区域。为了保护数据一致性,通常需要同步机制(如信号量)
4. 信号
信号是一种用于进程间传递通知的机制。它们可以用来通知进程某个事件的发生,如终止、暂停等
5. 套接字
套接字是一种强大的IPC机制,尤其适用于分布式系统中的网络通信。进程可以通过网络协议(如TCP/IP)进行通信
6. 信号量
信号量用于进程同步和互斥控制。它可以限制对共享资源的并发访问,防止数据竞争
9. C语言static什么时候用?extern什么时候用?static和extern的区别?
在C语言中,static
和extern
关键字用于控制变量和函数的链接和可见性
(1)static关键字
1. 局部变量
(1)作用:将局部变量声明为static
时,这个变量在函数调用之间保持其值不变,而不是每次调用函数时重新创建和初始化
(2)示例:每次调用example
函数时,count
的值都会递增,而不是重新初始化为0
void example() { static int count = 0; count++; printf("%d\n", count); }
2. 全局变量
(1)作用:将全局变量声明为static
时,这个变量只在声明它的文件中可见,即具有文件范围的可见性
(2)示例:globalVar
只能在其声明的文件中使用,其他文件无法访问
static int globalVar = 10;
3. 函数
(1)作用:将函数声明为static
时,这个函数只在声明它的文件中可见
(2)示例:
static void helperFunction() { // 仅在此文件中可见 }
(2)extern
关键字
1. 变量声明
(1)作用:extern
用于声明一个在其他文件中定义的变量。它告诉编译器这个变量的定义在另一个文件中。
(2)示例:
// file1.c int globalVar = 10; // file2.c extern int globalVar; void printVar() { printf("%d\n", globalVar); }
file2.c
文件中的extern int globalVar;
声明表示globalVar
在其他文件(如file1.c
)中定义。
2. 函数声明
(1)作用:默认情况下,函数的声明和定义是extern
的,即在一个文件中声明的函数可以在其他文件中使用而无需显式声明extern
(2)示例:
// file1.c void myFunction() { // 函数定义 } // file2.c extern void myFunction(); void callFunction() { myFunction(); // 调用file1.c中的函数 }
(3)static和extern的区别
1. 可见性(作用范围)
(1)static:限制变量或函数的作用范围,仅在声明它的文件或函数内可见
(2)extern:展变量或函数的作用范围,可以跨文件访问
2. 链接
(1)static:内部链接,仅在单个文件内部有效
(2)extern:外部链接,可以在多个文件之间共享
3. 用途
(1)static:用于需要在函数调用之间保持状态的局部变量,或限制全局变量和函数的作用范围以避免命名冲突
(2)extern:用于跨文件共享变量和函数
10. 冒泡排序法
(1)定义
冒泡排序(Bubble Sort)是一种简单的排序算法,它通过重复地遍历待排序的列表,依次比较相邻的两个元素,如果顺序错误就交换它们的位置。这个过程会持续进行直到整个列表有序为止。
(2)核心思想
通过多次遍历将最大的元素冒泡到列表的末尾
11. linux中栈默认有多大?
(1)在现代Linux系统中,用户级进程的栈大小通常是8MB(兆字节)
(2)对于Linux内核,内核栈的大小是固定的,对于x86_64架构,内核栈大小通常是16KB或32KB
12. 链表和数组的区别
(1)存储方式
1. 数组
数组中的元素在内存中是连续存储的。数组的大小在声明时就必须确定,并且在使用过程中不可改变
2. 链表
链表中的元素(称为节点)在内存中可以不连续存储。每个节点包含数据和指向下一个节点的指针。链表可以动态地调整大小,添加或删除元素时不需要移动其他元素
(2)内存使用
1. 数组
由于数组的内存是连续分配的,申请较大数组时可能会因为内存碎片问题而导致分配失败。此外,数组的大小固定,可能会造成内存浪费或不足
2. 链表
链表每个节点单独分配内存,节点只在需要时分配,因此更加灵活,但也会带来额外的内存开销(存储指针的空间)
(3)访问速度
1. 数组
数组支持快速的随机访问,任何元素的访问时间复杂度都是 (O(1)),因为可以直接通过索引访问
2. 链表
链表的随机访问较慢,时间复杂度为 (O(n)),因为需要从头节点开始逐个遍历到目标节点
(4)插入和删除
1. 数组
在数组中间插入或删除元素通常比较麻烦,因为需要移动其他元素,时间复杂度为 (O(n))
2. 链表
在链表中插入或删除元素比较高效,特别是当操作发生在链表的头部或尾部时,只需修改指针即可,时间复杂度为 (O(1))。然而,在链表中间位置操作时,仍然需要遍历,复杂度为 (O(n))
(5)空间利用率
1. 数组
数组大小固定,不容易调整,可能会导致内存浪费(分配多余的空间)或不足(需要更多的空间但无法扩展)
2. 链表
链表按需分配内存,可以更好地利用内存,但由于每个节点都有指针,额外的指针存储也会增加内存开销
(6)实现难度
1. 数组
数组的实现和使用较为简单,C语言标准库中直接支持
2. 链表
链表的实现相对复杂,需要处理指针的分配和释放,并防止内存泄漏。
(7)应用场景
1. 数组
适用于需要频繁随机访问的场景,如矩阵运算、查找算法等
2. 链表
适用于需要频繁插入和删除的场景,如队列、栈、图的邻接表表示等
13. linux查看进程的命令
(1)ps -ef
: 以完整格式显示所有进程
(2)top
命令提供了实时更新的系统进程列表,并显示了CPU和内存的使用情况
14. 应用层的协议都有什么?有什么作用?
(1)HTTP(超文本传输协议):用于在Web服务器和客户端之间传输超文本文档,支持万维网上的数据传输
(2)HTTPS(超文本传输安全协议):HTTP的安全版本,使用SSL/TLS加密通信内容,保证数据传输的安全性。
(3)FTP(文件传输协议):用于在网络上传输文件的标准协议,提供文件上传、下载和管理功能
(4)DNS(域名系统):用于将域名映射到IP地址的分布式数据库系统,使得用户能够通过域名访问网站。
(5)DHCP(动态主机配置协议):用于自动分配IP地址、子网掩码、网关等网络配置信息给计算机,方便网络中的设备进行通信
15. 插入排序的概念(以从小到大为例)
(1)从数组的第二个元素开始,依次处理每一个元素
(2)对于当前处理的元素,从元素左边第一个元素开始,将其与之前已经排好序的元素进行比较,将比当前元素大的元素向右移动,为当前元素腾出位置找到适当的位置插入。
(3)重复上述步骤,直到处理完所有元素
16. 查看网络状态用什么命令
(1)ifconfig:用于显示和配置网络接口
(2)ip addr show:用于显示和配置网络接口、路由、策略路由等
(3)ping www.baidu.com:用于测试与目标主机的连通性
17. ubuntu中的linux操作系统中,对目录的操作流程是什么/
18. 线程的作用
(1)提高程序的执行效率
多线程可以让程序并发地执行多个任务,从而提高了程序的执行效率。多个线程可以在多核处理器上并行运行,使得计算资源得到更充分的利用
(2)改善程序的响应性
在用户界面程序中,使用多线程可以使得程序在进行后台计算的同时,依然能够响应用户的输入。例如,一个线程可以负责处理用户输入,另一个线程可以进行复杂的计算或数据加载
(3)实现异步操作
线程可以用于实现异步操作,例如在网络编程中,一个线程可以负责发送和接收数据,而主线程可以继续处理其他任务,不必等待网络操作完成
(4)实现资源共享
同一进程中的多个线程可以共享进程的资源(如内存、文件句柄等),从而提高了资源利用率和数据处理效率。这使得线程间通信和数据共享变得更加方便和高效
(5)任务分解
线程使得复杂任务可以被分解为多个子任务,每个子任务由一个线程来完成。这样可以简化程序设计,并使得任务处理更加高效
19. 内存分布图?有哪几个区?
当一个进程被创建时,操作系统会给它分配一个独立的内存空间。这个内存空间通常分为以下几个主要部分:
(1)文本区:存储可执行程序的机器语言代码,通常是只读的。这部分包含了程序的指令
(2)数据区:存储全局变量、静态变量和常量。这部分可以进一步细分为初始化的数据段和未初始化的数据段(BSS段)
(3)堆区:动态分配的内存空间,用于存储程序运行时动态分配的数据。堆的大小不固定,可以在程序运行时动态调整
(4)栈区:存储函数的局部变量、函数参数和函数调用的上下文信息。栈是一个后进先出的数据结构,用于实现函数的调用和返回
(5)内核:用于存储操作系统内核的代码和数据,只能被操作系统内核访问
(6)堆栈段:用于存储函数调用和返回的地址,以及局部变量等信息
(7)共享库区/动态链接库区:存储共享库或动态链接库的代码和数据,多个进程可以共享这个区域,以节省内存
20. 信号的作用
(1)通知接收进程发生了特定事件
(2)进程间的同步操作:一个进程可以向另一个发送信号以通知其某个事件已发生
(3)处理进程中的错误或者异常情况
(4)终止进程
(5)进程管理:启动、停止、暂停、恢复进程等
(6)为进程进行定时和闹钟信号
(7)进程间通信
21. const用法
(1)定义
在C语言中,const
是一个关键字,用于定义常量。使用 const
关键字声明的变量被称为常量,其值在程序执行期间不能被修改
(2)用法
1. 定义常量
const int MAX_VALUE = 100; const float PI = 3.14159;
2. 函数参数中的const
void printMessage(const char *message) { printf("%s", message); }
这里 const char *message
表示指向字符型常量的指针,message
指向的内容在函数中不会被修改
3. 常量指针
int value = 10; const int *ptr = &value; // ptr 是一个指向整型常量的指针
4. 指向常量的指针
int value = 20; int *const ptr = &value; // ptr 是一个指向整型变量的常量指针
5. 常量修饰函数返回值
const int getValue() { return 42; }
6. 常量全局变量
const int GLOBAL_CONST = 50;
7. 常量数组
const int arr[] = {1, 2, 3, 4, 5};
22. c语言中Volatile的定义和作用
(1)定义
在C语言中,volatile
是一个关键字,用于告诉编译器该变量的值可能会在程序执行过程中被意外地改变,因此编译器不应该对这个变量进行优化。volatile
常用于描述那些可以被意外更改的变量,例如硬件寄存器、多线程共享变量或者信号处理器等
volatile int sensorValue;
(2)作用
1. 禁止编译器优化
volatile
告诉编译器,该变量的值可能会在编译器可见范围之外的地方发生改变,因此编译器不应该对其进行优化,以避免因为优化而导致意外行为。
2. 多线程通信
在多线程编程中,volatile
也可以用来在不同的线程之间传递数据,确保数据的可见性
3. 硬件寄存器访问
当处理硬件寄存器等可能在编译器控制之外发生变化的变量时,使用 volatile
可以确保每次访问都会重新从内存中加载变量的值,而不是使用寄存器中的缓存值。
4. 信号处理器
在信号处理程序中,一般需要将被信号回调修改的变量声明为 volatile
,以确保程序正确地处理信号
23. TCP为什么安全可靠?
TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层协议
(1)数据完整性
TCP使用校验和来验证数据的完整性。在数据传输过程中,数据被分为多个数据包,每个数据包都有一个校验和。接收端会对接收到的数据包进行校验和计算,如果发现数据包损坏,就会要求重新发送
(2)确认机制
TCP使用确认机制确保数据的可靠传输。当发送端发送数据后,接收端会发送确认消息,告诉发送端数据已经接收。如果发送端在合适的时间内没有收到确认,它会重发数据
(3)流量控制
TCP通过使用滑动窗口协议进行流量控制,确保发送方发送的数据不会超过接收方处理能力。这样可以避免数据丢失和网络拥塞
(4)拥塞控制
TCP使用拥塞控制算法来避免网络拥塞。通过动态调整发送数据的速率,TCP可以在网络出现拥塞时减缓发送速度,以保证网络的稳定性
(5) 顺序传输
TCP保证数据包按照发送的顺序到达接收端,避免了数据乱序的情况
(6)三次握手和四次挥手
TCP建立连接时使用三次握手来确保双方都准备好数据传输,而关闭连接时使用四次挥手来优雅地关闭连接,避免数据丢失和资源泄漏
(7)可靠性
TCP提供了端到端的可靠性传输,即使在网络发生故障或数据包丢失的情况下也可以恢复数据传输
24. 应用层的协议有哪些?
(1)HTTP
用于在Web浏览器和Web服务器之间传输超文本文档,是万维网的核心协议
(2)HTTPS
在HTTP的基础上增加了加密和身份验证机制,通过SSL/TLS来保护数据传输的安全性
(3)FTP
用于在客户端和服务器之间传输文件
(4)SMTP
用于在邮件服务器之间传递电子邮件
(5)POP3
用于接收电子邮件,将邮件从邮件服务器传输到客户端
(6)IMAP
也用于接收电子邮件,与POP3不同的是,IMAP在客户端和邮件服务器之间保留邮件的副本
(7)DNS
用于将域名映射为IP地址,实现域名解析
(8)DHCP
用于动态分配IP地址和其他网络配置参数给设备
(9)SNMP
用于网络设备管理、监控和信息采集
(10)WebSocket
提供全双工通信的协议,用于实时的Web应用程序
25. 线程间为什么要加锁?
(1)保护共享资源
当多个线程同时访问共享资源时,如果没有适当的同步机制,可能会导致数据竞争和不一致性。通过加锁,可以确保在任何时候只有一个线程可以访问共享资源,避免数据混乱
(2)确保操作的原子性
某些操作需要原子执行,即不可被中断。通过加锁,可以确保这些操作在任何时候都是原子执行的,不会被其他线程打断
(3)避免死锁
通过合理设计和使用锁,可以避免死锁的发生。死锁是指两个或多个线程互相等待对方持有的资源而无法继续执行的情况
(4)保护临界区
临界区是指一段代码,只能被一个线程同时执行,以防止多个线程同时执行可能导致问题的代码段。使用锁可以保护临界区,确保在同一时间只有一个线程可以执行其中的代码
(5)确保数据的一致性
在涉及到多线程操作的情况下,通过加锁可以确保数据的一致性,避免数据更新中出现的异常情况
(6)提高程序可靠性和稳定性
26. strcpy和strncpy的区别
(1)strcpy(目标缓冲区要比源字符串大)
1. strcpy用于将一个字符串复制到另一个字符串中,直到遇到空字符\0
2. 如果源字符串比目标字符串长,strcpy会继续复制直到遇到源字符串的空字符,并可能导致目标字符串缓冲区溢出
3. strcpy不提供截断或限制复制的功能,因此需要确保目标缓冲区足够大以容纳整个源字符串
char dest[20]; char source[] = "Hello, World!"; strcpy(dest, source); // 将 source 复制到 dest 中
(2)strnpy
1. strnpy用于将一个字符串的一部分复制到另一个字符串中,并最多复制指定的字符数
2. 第三个参数指定要复制的最大字符数,即使源字符串比这个数目长也会在指定的字符数后停止复制,如果源字符串长度小于指定字符数,则用空字符填充
3. strnpy能够避免目标缓冲区溢出的风险,但需要注意可能会受到空字符填充的影响
char dest[10]; char source[] = "Hello, World!"; strncpy(dest, source, sizeof(dest) - 1); // 将 source 的一部分复制到 dest 中,最多复制 sizeof(dest) - 1 个字符 dest[sizeof(dest) - 1] = '\0'; // 手动添加空字符,以确保目标字符串以空字符结尾
总的来说,strnpy更安全一些,可以避免缓冲区溢出问题,而strcpy则更简洁,但需要确保目标缓冲区足够大
27. 搭建简单的http服务器
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #define PORT 8080 #define BUFFER_SIZE 1024 void handle_client(int client_socket) { char buffer[BUFFER_SIZE]; int read_size; // 读取客户端请求 read_size = recv(client_socket, buffer, BUFFER_SIZE, 0); if (read_size > 0) { // 打印请求信息 buffer[read_size] = '\0'; printf("Received request:\n%s\n", buffer); // 构造HTTP响应 const char *response = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Content-Length: 13\r\n" "\r\n" "Hello, World!"; // 发送响应 send(client_socket, response, strlen(response), 0); } // 关闭客户端连接 close(client_socket); } int main() { int server_socket, client_socket; struct sockaddr_in server_addr, client_addr; socklen_t client_addr_len = sizeof(client_addr); // 创建服务器套接字 server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket < 0) { perror("Socket creation failed"); exit(EXIT_FAILURE); } // 绑定套接字到指定端口 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("Bind failed"); close(server_socket); exit(EXIT_FAILURE); } // 监听端口 if (listen(server_socket, 10) < 0) { perror("Listen failed"); close(server_socket); exit(EXIT_FAILURE); } printf("Server is listening on port %d...\n", PORT); // 主循环,接受并处理客户端连接 while (1) { client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len); if (client_socket < 0) { perror("Accept failed"); continue; } handle_client(client_socket); } // 关闭服务器套接字 close(server_socket); return 0; }
28. c语言中声明和定义的区别
(1)声明
声明是告诉编译器某个变量、函数或类型的存在和它的类型信息,但并不分配内存或提供实现。声明通常出现在头文件中
extern int x; // 变量声明,表示某处有一个名为 x 的 int 型变量 int add(int, int); // 函数声明,表示某处有一个名为 add 的函数,接受两个 int 型参数并返回 int 型结果
在上述示例中,extern int x;是一个变量声明,它告诉编译器在其他地方有一个名为x的int变量,但并不在此分配内存或赋值。同样,int add(int,int);是一个函数声明,它告诉编译器有一个名为add函数,但并不提供该函数的实现
(2)定义
定义是实际创建变量或函数并为其分配内存或提供实现。定义不仅告诉编译器某个标识符的类型信息,还会分配内存空间(对于变量)或提供具体的实现(对于函数)
int x = 10; // 变量定义,分配内存并初始化 int add(int a, int b) // 函数定义,提供具体实现 { return a + b; }
在上述实例中,int x = 10;是一个变量定义,它不仅告诉编译器x是一个int型变量,还为x分配内存并初始化为 10。同样,int add(int a,int b)是一个函数定义,它提供了add函数的具体实现
总结
- 声明:只提供类型信息,不分配内存或实现功能。
- 定义:提供类型信息,并分配内存或实现功能。
声明和定义的区别主要在于是否分配内存和提供实现。一个变量或函数可以有多次声明,但只能有一次定义
29. 使用进程的目的和作用
(1)资源隔离
进程具有独立的地址空间,这意味着一个进程不能直接访问另一个进程的内存。这种隔离有助于提高系统的稳定性和安全性,因为一个进程的错误不会直接影响到其他进程
(2)并行执行
通过使用多个进程,程序可以同时执行多个任务。这种并行执行可以提高应用程序的性能,特别是在多核处理器上,每个进程可以在不同的处理器核上独立运行,从而充分利用硬件资源
(3)提高系统可靠性
进程隔离使得一个进程的崩溃不会影响到其他进程。这种隔离有助于构建更加可靠的系统。例如,在Web服务器中,每个请求可以由一个独立的进程处理,这样即使一个请求处理失败,也不会影响其他请求的处理
(4)简化编程模型(多文件编程)
使用进程可以简化一些复杂任务的编程模型。例如,在一些应用程序中,不同的模块可能需要不同的权限或资源,通过将这些模块分成不同的进程,可以更容易地管理和分配资源
(5)多用户支持
进程允许多个用户在同一系统上同时运行各自的程序。操作系统可以为每个用户创建独立的进程,从而确保每个用户的程序运行在各自独立的地址空间中,互不干扰。
(6)任务管理和调度
操作系统使用进程来管理和调度任务。进程调度程序决定哪个进程应该在任何给定时间运行,从而实现多任务处理和资源优化利用。
(7)开发和调试
进程的独立性使得开发和调试更加容易。开发人员可以在不影响系统其他部分的情况下启动、停止和调试进程。这种独立性还使得错误隔离更加容易,便于发现和修复问题
(8)安全性
进程隔离有助于提高系统的安全性。敏感操作可以放在独立的进程中运行,限制其他进程对其资源的访问。这种设计可以减少系统被攻击的风险,因为恶意代码在一个进程中的影响范围被限制在该进程内
30. 内存碎片、内存泄漏和内存溢出的定义和区别
(1)内存碎片
内存碎片是指由于频繁的内存分配和释放操作,导致内存中空闲块被分割成许多不连续的小块,从而无法满足较大内存块的分配需求,尽管总的空闲内存量可能足够
(2)内存泄漏(malloc之后没有free)
内存泄漏是指程序在动态分配内存后没有释放它,从而导致这些内存无法再被使用或回收。随着时间的推移,内存泄漏会导致系统可用内存逐渐减少,最终可能导致程序或系统崩溃
(3)内存溢出
内存溢出是指程序尝试使用超过可用内存范围的内存,从而导致程序崩溃或产生不可预测的行为。内存溢出通常发生在堆栈空间耗尽或动态内存分配失败时
1. 栈溢出(递归过多)
当函数递归调用过深或局部变量过多时,可能会导致栈空间耗尽,从而引发栈溢出
2. 堆溢出(malloc过大)
当程序动态分配的内存超过系统可用内存时,会导致堆溢出
31. IO学了什么?
(1)文件IO
包括打开、读取、写入、关闭文件等操作。在Linux中,文件被视为一种特殊类型的设备
(2)套接字(socket)IO
用于网络通信,可以通过套接字进行数据的发送和接收
(3)标准IO
包括标准输入(stdin)、标准输出(stdout)和标准错误(stderr),用于处理用户输入和程序输出
(4)管道(Pipe)IO
用于在进程间进行通信,包括匿名管道和命名管道
(5)设备IO
用于与硬件设备进行通信,包括磁盘、网络接口、串行端口等
(6)内存映射IO
将文件或设备映射到内存中,实现对文件内容的直接访问
(7)控制台IO
用于与终端设备进行交互,包括控制台输入和输出
32. 在Linux操作系统中怎么获得内存的大小?
(1)free
使用free
命令可以显示系统当前的内存使用情况,包括总内存、已用内存、空闲内存等。在终端中输入以下命令可以查看内存信息
free -h
(2)top
top
或 htop
命令可以显示系统的实时进程和资源使用情况,包括内存使用情况
33. 库函数用man1、man2和man3查询有什么区别?
(1)man1:用户命令
(2)man2:系统调用
(3)man3:库函数
34. c语言和linux高级编程的区别
C语言是一种编程语言
Linux高级编程则是指在Linux环境下进行程序开发的技术。Linux高级编程涉及到对Linux系统的深入理解,包括文件系统操作、进程管理、网络编程、系统编程等方面的知识
35. fgets和fputs的文件拷贝是怎么实现的?
(1)打开源文件和目标文件,一个用于读取内容,一个用于写入内容
(2)使用 fgets
从源文件逐行读取内容
(3)使用 fputs
将读取的内容逐行写入到目标文件中
(4)重复步骤2和步骤3,直到源文件全部读取完毕
(5)重复步骤2和步骤3,直到源文件全部读取完毕
#include <stdio.h> int main() { FILE *source_file = fopen("source.txt", "r"); FILE *target_file = fopen("target.txt", "w"); if (source_file == NULL || target_file == NULL) { perror("Error opening file"); return 1; } char buffer[1024]; while (fgets(buffer, sizeof(buffer), source_file) != NULL) { fputs(buffer, target_file); } fclose(source_file); fclose(target_file); printf("File copied successfully.\n"); return 0; }
36. 常用的数据结构有哪些
(1)数组(Array):一组按照顺序存储的相同类型数据元素集合。数组的访问速度快,但大小固定
(2)链表(Linked List):由节点组成的集合,每个节点包含数据和指向下一个节点的指针。链表可以动态分配内存,插入和删除元素效率高
(3)栈(Stack):先进后出(FILO)的数据结构,只能在栈顶进行插入和删除操作
(4)队列(Queue):先进先出(FIFO)的数据结构,只能在队尾插入,在队首删除元素
(5)树(Tree):由节点构成的层次结构,包括二叉树、二叉搜索树、平衡树、红黑树等。树用于模拟具有层次关系的数据
(6)图(Graph):由节点(顶点)和边(边缘)组成的数据结构,用于表示各种关系
(7)哈希表(Hash Table):通过哈希函数将键映射到值的数据结构,实现快速的查找和插入操作
(8)堆(Heap):一种特殊的树形数据结构,包括最小堆和最大堆,用于高效地查找最大或最小元素
37. 栈和队列有什么相同点
(1)都是线性数据结构:栈和队列都是线性数据结构,数据元素按照一定的顺序排列
(2)支持特定操作:栈和队列都支持特定的操作,例如压栈(Push)和出栈(Pop)对于栈,入队(Enqueue)和出队(Dequeue)对于队列
(3)限制操作位置:栈和队列都有限制在何处插入或删除元素的规则。栈只能在栈顶进行插入和删除操作(后进先出,LIFO),而队列只能在队尾插入,在队首删除元素(先进先出,FIFO)
38. 进程状态的切换有哪几种?
(1)就绪(Ready):当进程具备运行的条件,等待被调度执行时,处于就绪状态。这表示进程已经准备好运行,只需分配CPU时间
(2)运行(Running):正在被处理器执行的进程处于运行状态。在任何给定的时间点,只有一个进程能够在单个处理器上运行
(3)阻塞(Blocked):当进程等待某个事件发生,例如等待I/O操作完成或等待消息发送时,会进入阻塞状态。在此状态下,进程暂时停止执行,直到等待的事件发生
(4)终止(Terminated):进程执行完毕或者由于某种原因被终止,处于终止状态。在这种状态下,进程将被系统移出正在运行的进程队列,并释放其占用的系统资源
39. 父进程和子进程运行期间的关系是什么?
(1)父子进程共享代码段:在许多操作系统中,父进程和子进程会共享代码段。这意味着子进程在创建时会复制父进程的代码段,这样可以减少系统资源的占用
(2)父子进程共享文件描述符:父进程和子进程也会共享文件描述符。这意味着如果一个进程打开了一个文件,子进程也可以访问该文件,除非文件描述符被显式关闭
(3)父子进程独立的数据空间:尽管父子进程共享代码段,但它们通常具有独立的数据空间。这意味着它们的变量、堆栈等数据结构是独立的,一个进程对这些数据的修改不会影响另一个进程
(4)进程ID关系:每个进程都有一个唯一的进程标识符(PID)。在一个子进程创建时,它会继承父进程的PID,并且有一个独立的子进程ID
(5)关系解除:当一个进程终止时,它的所有子进程会成为孤儿进程,操作系统将这些孤儿进程的父进程设置为init进程(在Unix系统中通常是PID为1的进程)。这样确保系统中的所有进程都有一个父进程
40. linux中目录操作有哪几种?
(1)pwd
(Print Working Directory) 显示当前所在的目录路径
(2)ls
列出当前目录中的文件和子目录。
(3)ls -l
以详细信息列表显示文件和目录。
(4)ls -a
显示包括隐藏文件在内的所有文件和目录
(5)cd
(Change Directory) 改变当前工作目录
(6)mkdir
创建一个新目录
(7)rmdir
删除一个空目录、rm -r
递归删除目录及其内容。
(8)mv
移动或重命名目录
(9)tree
显示目录结构树(需要安装tree
工具)
41. 编译程序什么时候会用多任务?(makefile)
在编译程序时使用多任务(也称为并行编译)可以显著加快编译速度,尤其是对于大型项目
(1)大型代码库:当项目规模很大,包含很多源文件和模块时,逐个编译这些文件会非常耗时。使用多任务编译可以同时编译多个文件,从而加快整体编译速度
(2)多核处理器:现代计算机通常具有多核CPU。如果编译过程只是单线程运行,那么只有一个CPU核心在工作,而其余核心则处于空闲状态。多任务编译可以充分利用所有可用的CPU核心,提高编译效率
(3)持续集成/持续部署(CI/CD):在CI/CD管道中,快速完成编译和测试是非常重要的。使用多任务编译可以缩短每次构建的时间,从而加快开发和发布的周期
(4)开发人员生产力:在日常开发中,频繁的编译是常见的。通过加快编译过程,开发人员可以更快地得到反馈,提高生产力
42. 编译多任务程序时是首选进程还是线程
(1)进程
1. 优点
(1)隔离性好:进程拥有独立的地址空间,彼此之间不共享内存,因此一个进程崩溃不会影响其他进程
(2)并行度高:在多核处理器上,每个进程可以独立运行,充分利用多核的优势
(3)平台独立:大多数编译工具链(如Makefile、Ninja)默认使用进程来实现并行编译,因为它们在不同操作系统上都可以一致地工作
2. 缺点
(1)开销大:进程间通信(IPC)和进程切换的开销较高,特别是进程数目很多时
(2)资源占用高:每个进程都有自己的资源(内存、文件句柄等),资源占用较高
(2)线程
1. 优点
(1)轻量级:线程之间共享地址空间和资源,创建和销毁的开销较小
(2)通信方便:线程之间共享内存,数据传递更加高效
2. 缺点
(1)安全性差:由于线程共享内存,数据同步和线程安全问题需要额外处理,可能引入复杂性
(2)可移植性:线程的实现和管理在不同的操作系统上存在差异,跨平台编译工具往往选择进程而非线程
大多数编译工具链选择使用进程而不是线程来实现多任务编译,主要原因是进程的隔离性和跨平台支持更好。尽管进程的开销较大,但在编译任务中,这种开销通常是可以接受的。线程虽然轻量级,但需要处理更多的同步和安全问题,因此在编译任务中并不常见。
因此,在大多数情况下,编译多任务程序时,首选使用进程而不是线程。如果你在开发自己的编译工具或需要进行非常细粒度的并行任务调度,才可能考虑使用线程
43. 查数据库如何升序打印?(按grade列?)
44. 编译程序如何选择数据库?
编译程序通常不会直接选择数据库,因为编译程序的主要功能是将高级语言代码翻译成机器语言。数据库是用来存储数据的软件,一般用于应用程序的数据存储和管理。在开发应用程序时,开发人员会根据应用程序的需求和特性来选择最适合的数据库
(1)数据模型:根据应用程序的数据结构和关系模型,选择适合的数据库类型,如关系型数据库(如MySQL、PostgreSQL)、NoSQL数据库(如MongoDB、Cassandra)
(2)性能要求:根据应用程序对读取和写入数据的频率和量,选择具有高性能的数据库系统
(3)可靠性和稳定性:考虑数据库系统的可靠性和稳定性,以确保数据的安全性和持久性
(4)扩展性:如果应用程序需要处理大量数据或需要随着业务增长进行扩展,选择支持水平和垂直扩展的数据库系统
(5)数据库管理经验:考虑团队中开发人员对不同数据库系统的熟悉程度和经验,以便于开发、部署和维护数据库
(6)成本:考虑数据库系统的许可成本、维护成本和扩展成本,选择符合预算的数据库系统
(7)生态系统和支持:考虑数据库系统的生态系统和支持情况,如社区支持、文档和工具等
45. 语言编程中不想遍历元素但是想找到某些特定元素怎么实现?
在C语言编程中,如果你不想遍历整个数据结构(比如数组或链表),而是想要找到特定元素,可以考虑使用一些高效的查找算法。以下是一些常用的查找算法
(1)二分查找(Binary Search):适用于已排序的数组,通过不断将查找范围减半,快速定位目标元素
(2)哈希表(Hash Table):利用哈希函数将元素映射到哈希表中的位置,以快速检索元素
(3)二叉搜索树(Binary Search Tree):根据节点值的大小关系构建二叉树,可以快速定位目标元素
46. 怎么定义一个整形5个元素的数据指针?(int (*ptr)[5])
(1)在C语言中,数组指针是指向数组的指针,它可以用来访问数组中的元素。
(2)定义一个整型5个元素的数组指针可以通过以下方式实现
int arr[5] = {1, 2, 3, 4, 5}; int (*ptr)[5]; // 定义一个指向包含5个整数的数组的指针 ptr = &arr; // 指向数组arr
47. http交互报文的格式?
HTTP协议是一种用于传输超文本的应用层协议,它定义了客户端和服务器之间交换数据的格式。HTTP通信通常由两部分组成:请求报文和响应报文。下面是它们的基本格式
(1)HTTP请求报文格式
<method> <request-target> <HTTP-version> <Headers> <Blank line> <Body> GET / HTTP/1.1\r\n Host: www.example.com\r\n \r\n
<method>
:指定请求的类型,比如GET、POST、PUT、DELETE等。<request-target>
:指定请求的目标资源的URI路径。<HTTP-version>
:指定所使用的HTTP版本,通常是"HTTP/1.1"。<Headers>
:包含各种请求头(headers),如Host、User-Agent、Content-Type等。<Blank line>
:空行,用于分隔请求头和请求体。<Body>
:请求的内容体,比如POST请求中的表单数据
(2)HTTP响应报文格式
<HTTP-version> <status-code> <reason-phrase> <Headers> <Blank line> <Body>
<HTTP-version>
:指定使用的HTTP版本,通常是"HTTP/1.1"。<status-code>
:指示请求的处理状态,比如200表示成功,404表示未找到等。<reason-phrase>
:对状态码的简短描述。<Headers>
:包含各种响应头(headers),如Content-Type、Content-Length等。<Blank line>
:空行,用于分隔响应头和响应体。<Body>
:响应的正文,通常包含请求的结果数据。
48. 指针数组和数组指针的区别?
(1)指针数组(int *ptrArray[5]
)
指针数组是指一个数组,其元素是指针。每个元素都可以指向一个特定类型的数据。例如,int *ptrArray[5]
定义了一个包含5个指向整数的指针的数组。可以这样理解:指针数组是一个数组,每个元素指向不同的内存位置
int *ptrArray[5]; // 声明一个包含5个整型指针的数组 int var1 = 10, var2 = 20, var3 = 30; ptrArray[0] = &var1; // 第一个元素指向var1 ptrArray[1] = &var2; // 第二个元素指向var2 ptrArray[2] = &var3; // 第三个元素指向var3
(2)数组指针(int (*ptr)[5])
数组指针是指一个指针,指向一个数组。在声明时,指针的类型应该是指向数组的指针。例如,int (*ptr)[5]
定义了一个指向包含5个整数的数组的指针
int arr[5] = {1, 2, 3, 4, 5}; int (*ptr)[5]; // 定义一个指向包含5个整数的数组的指针 ptr = &arr; // 指向数组arr
- 指向的对象不同:指针数组的每个元素都是一个指针,而数组指针指向一个数组。
- 类型声明不同:指针数组声明中,中括号
[]
出现在指针名字右边;数组指针声明中,中括号[]
出现在*
的右边。 - 访问方式不同:指针数组的元素可以通过索引访问,而数组指针需要使用指针运算符*来访问数组元素
49. C语言中什么时候使用二级指针?
(1)动态分配内存
当需要在运行时动态分配内存以存储数据结构时,可以使用二级指针。通过传递指向指针的指针,可以在函数内部分配内存并将结果返回给调用者
#include <stdio.h> void allocateMemory(int **ptr) { *ptr = (int *)malloc(sizeof(int)); **ptr = 42; // 将值存储在 *ptr 指向的地址上 } int main() { int *ptr; allocateMemory(&ptr); printf("Value: %d\n", *ptr); free(ptr); // 释放内存 return 0; }
(2)多维数组
二维或多维数组实际上是以一维数组的形式存储在内存中的。通过使用二级指针,可以更灵活地访问和操作多维数组的元素
(3)修改传递给函数的参数
语言中函数参数传递是按值传递的,如果需要在函数中修改指针指向的内容,必须使用指向指针的指针
(4)链表或树等数据结构
在实现像链表、树等动态数据结构时,经常需要使用二级指针操作节点间的连接关系
(5)字符串处理
处理字符串时,有时候需要修改指向字符串的指针本身,而不仅仅是字符串内容
50. linux中进程相关的命令都有什么?
(1)ps:显示当前进程的状态信息
ps aux
:显示所有进程的信息。ps -ef
:以完整格式显示所有进程的信息
(2)top:实时显示系统中各个进程的资源使用情况
(3)pstree:以树状结构显示进程的父子关系
(4)pidof:获取进程的PID
(5)kill:向进程发送信号(通常用于终止进程)
kill PID
:发送默认的TERM信号终止指定的PID进程。kill -9 PID
:发送KILL信号强制终止指定的PID进程
(6)killall:根据进程名称终止所有匹配的进程
killall process_name
:终止所有名称为process_name
的进程
51. 什么是内存大小端?如何测试大端?
(1)大端存储:内存低地址处存放高数据位,内存高地址处存放低数据位
(2)小端存储:内存低地址处存放低数据位,内存高地址处存放高数据位
(3)程序判断内存大小端
#include <stdio.h> int main(void) { int num = 0x11223344; char *p = (char *)# if (0x11 == *p) { printf("大端!\n"); } else if (0x44 == *p) { printf("小端!\n"); } return 0; }
#include <stdio.h> union u { char a; int b; }; int main(void) { union u u1; u1.b = 1; if (u1.a) { printf("小端!\n"); } else { printf("大端!\n"); } return 0; }
52. epoll和poll的定义和区别?
epoll
和poll
是Linux内核提供的两种I/O多路复用机制,用于处理多个文件描述符上的I/O事件
(1)poll
1. 定义
poll
是POSIX标准的一部分,用于监控多个文件描述符,以查看它们是否有I/O操作准备就绪。其函数原型如下:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds
:一个pollfd
结构体数组,每个元素表示一个要监控的文件描述符及其对应的事件。nfds
:数组的大小。timeout
:等待时间(毫秒),-1表示无限等待
2. 使用
(1)创建一个pollfd
数组,并填入要监控的文件描述符及其感兴趣的事件。
(2)调用poll
函数,传入pollfd
数组及其大小。
(3)处理返回的事件
(2)epoll
1. 定义
epoll
是Linux特有的用于I/O多路复用的机制,相比poll
和select
,它在处理大量文件描述符时有更好的性能。其主要接口包括
epoll_create1
:创建一个epoll实例。epoll_ctl
:向epoll实例中添加、修改或删除文件描述符。epoll_wait
:等待事件的发生
2. 使用
(1)创建一个epoll实例
int epoll_fd = epoll_create1(0);
(2)向epoll实例中添加文件描述符及其感兴趣的事件
struct epoll_event event; event.events = EPOLLIN | EPOLLOUT; event.data.fd = fd; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);
(3)等待事件的发生并处理
struct epoll_event events[MAX_EVENTS]; int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for (int i = 0; i < n; i++) { if (events[i].events & EPOLLIN) { // 处理读事件 } if (events[i].events & EPOLLOUT) { // 处理写事件 } }
(3)poll和epoll区别
1. 性能
poll
在每次调用时都需要遍历整个文件描述符数组,因此当文件描述符数量较多时,性能下降明显。epoll
通过事件通知机制,只返回有事件的文件描述符,且内部使用红黑树和链表优化,因此在处理大量文件描述符时性能更好
2. 可扩展性
poll
在文件描述符数量增加时,性能会线性下降。epoll
可以高效地处理数万个文件描述符,性能影响较小
3. API接口(使用复杂性)
poll
使用简单,适合处理较少文件描述符的场景。epoll
提供了更灵活和复杂的接口,适合需要高性能I/O的场景
4. 事件触发模式
poll
采用水平触发(level-triggered)模式。epoll
支持水平触发和边缘触发(edge-triggered)模式,边缘触发模式可以减少重复的事件通知,提高性能
适用场景
- 对于处理少量文件描述符的简单应用,
poll
使用更方便。 - 对于需要处理大量并发连接(如高性能服务器),
epoll
是更好的选择。
总结来说,epoll
在处理大量文件描述符时的性能和可扩展性方面比poll
更优越,但也更复杂,需要更细致的编程处理
53. 什么是长连接和短连接
长连接和短连接是计算机网络中的两种不同的连接方式,它们在HTTP协议中尤为常见
(1)短连接
每次请求都会建立一个新的连接,处理完请求后立即关闭连接。HTTP/1.0协议默认使用短连接
(2)长连接
建立连接后,多个请求复用同一个连接,直到客户端或服务端主动关闭连接。
HTTP/1.1协议默认使用长连接,并通过Connection: keep-alive
头来实现
54. 四种IO模型
(1)阻塞IO
1. 举例
read(读管道)、gets(标准输入输出)等
2. 特点
(1)效率高,没有IO事件时,任务被挂起,不占用CPU资源
(2)多个阻塞IO放在一起时,会导致相互受到影响
(2)非阻塞IO
1. 使用fcntl函数接口:能够修改文件描述符的属性为非阻塞
flags = fcntl(fd, F_GETFL); //获得文件描述符的属性 flags |= O_NONBLOCK; //在原来属性的基础之上加上非阻塞的标志 fcntl(fd, F_SETFL, flags); //将属性设置回去
2. 特点
(1)效率低,当没有IO事件的适合,CPU一直轮询判断是否有IO事件的发生
(2)可以解决多个阻塞导致程序编写的顺序性问题
(3)异步IO(发信号的方式通知)
(同步:多个任务有先后执行的顺序关系、异步:多个任务相互独立,没有先后执行的顺序关系)
1. 使用fcntl函数接口:能够修改文件描述符的属性为异步IO, 当文件描述符有IO事件产生的时候,给自己的进程发信号通知,通过编写信号捕捉函数进行想要的操作
flags = fcntl(fd, F_GETFL); flags |= O_ASYNC; //设置异步IO形式 fcntl(fd, F_SETFL, flags); //当有IO事件产生的时候会发信号 fcntl(fd, F_SETOWN, getpid()); //当有IO事件产生的时候会给自己的进程发信号
2. 特点
(1)效率高,当有IO事件时,内核会向应用层发送SIGIO信号,此时可以操作IO事件
(2)只能操作一个文件描述符,多个文件描述符无法区分
(4)多路复用IO
1. select(能够处理多个客户端对一个服务端的并发处理)
(1)定义
select是一种系统调用,用于监视多个文件描述符,查看是否有任何文件描述符已经做好读、写或发生了异常事件
(2)原理
1. 建立文件描述符集合,将需要监听的文字描述符加入描述符集合数组,阻塞监听所有的文件描述符
2. 产生事件的文件描述符会留在集合中,未产生事件的文件描述符会被剔除出集合
3. 最后判断想要监听的文件描述符是否在集合中,如果在集合中,可以读取其中的数据
select分别使用三个文件描述符集来监视读、写和异常事件。它会阻塞程序,直到其中一个文件描述符变得可用或超时
(3)缺点(相对于epoll)
1. select监听文件描述符集合(数组)有上限(8192文件描述符上限)
2. select产生的事件数据需要从内核层向用户层拷贝,增大资源开销
3. select不知道具体是哪个文件描述符产生事件,需要手动轮询判断产生事件的文件描述符
4. 只能工作在水平触发(直到把数据读取完才结束通知)模式(低速模式)
(1)水平触发方式(低速模式)(一直通知,直到接到通知为止)
在水平触发模式下,select
每次调用时都会检查所监控的文件描述符的当前状态,并将其返回给调用者。只要文件描述符处于可读、可写或有错误状态,select
就会不断地报告这些状态。如果一个文件描述符一直处于可读状态(比如有未读取的数据),每次调用 select
都会报告这个文件描述符可读,直到数据被读取完毕
(2)边沿触发模式(高速)
同一事件只通知一次,不会通知多次
2. poll
(1)定义
poll是另一种系统调用,功能与select类似,用于监视多个文件描述符上的事件
(2)原理
通过构建结构体数组,将需要监听的文字描述符以及事件加入到结构体数组中,监视文件描述符及其相关的事件,轮询判断当IO事件是否到来,若事件到来,会置位结构体中的revents,是事件是否到来的标志。当事件到来后,读取对应文字描述符所携带的数据,并将此文件描述符剔除出结构体数组。poll比select更加灵活,poll使用链表进行维护,所以它不受文件描述符数量限制
(3)优点
内部维护的是一个链表,不受文件描述符上限限制
(4)缺点
1. poll监听的事件集合在用户层,产生事件时的数据需要从内核层传递到用户层,增大了系统开销
2. poll需要手动监测获得产生事件的文件描述符
3. poll只能工作在水平触发模式(低速模式)
3. epoll(默认水平触发)
(epoll创建的事件表在内核层、之前select创建的文件描述符集合和poll创建的字符结构体数组都在用户层)
(1)定义
epoll是一种IO多路复用机制,通过事件通知的方式来管理多个文件描述符。有三个系统调用的API函数接口epoll_create、epoll_ctl、epoll_wait
(2)原理
1. 使用epoll_create创建一个用来监听事件的内核事件表、通过epoll_ctl将多个文字描述符加入内核事件表
2. 新建实际产生事件的表,使用epoll_wait,功能是等待事件发生,将发生事件的文字描述符和事件加入实际产生的事件表中,函数返回产生事件的个数,最后遍历实际产生事件的事件表,读取数据或进行相应的操作
(3)优点
1. epoll创建的是内核事件表,产生事件时,内核层中数据传递的资源开销小
2. epoll监听的事件个数不受限制
3. epoll直接返回所有产生事件的文件描述符,不需要手动判断
4. epoll可以工作在边沿触发模式(高速模式),只提醒一次
55. ARM的移植、剪裁、和中断处理机制
(1)ARM的移植
1. 引导程序(bootloader)
引导程序负责在系统启动时初始化硬件环境并加载操作系统
嵌入式系统常用的引导加加载程序是u-boot
2. 操作系统的移植
(1)修改内核源代码,使其适应特定的硬件平台
(2)配置和编译内核
(3)编写和配置设备驱动程序
3. 交叉编译工具链
arm-linux-gcc
(2)ARM的裁剪
在嵌入式系统中,资源有限,因此需要对系统进行裁剪以去除不必要的功能和代码,提高系统效率
1.裁剪内核
通过配置内核编译选项,可以选择性包含或排除特定的功能模块,例如,在linux的内核中,可以使用menuconfig工具来配置内核
2. 精简库和应用程序
(3)中断应用程序
ARM架构的中断处理机制包括硬件中断和软件中断
1. 中断执行流程
(1)中断源发出中断请求
(2)CPU查询中断是被运行,以及中断是否被屏蔽
(3)CPU考察中断优先级
(4)CPU保护现场
(5)执行中断服务函数
(6)恢复现场
2. 中断控制器
ARM处理器通常集成了一个中断控制器(如GIC,通用中断控制器)。中断控制器负责管理和分配中断请求,并将中断信号传递给CPU
3. 中断向量表
中断向量表是一个存储中断处理程序入口地址的数组。当中断发生时,CPU通过查找中断向量表来确定对应的中断处理程序
4. 中断处理程序
中断处理程序是响应中断请求的代码。当中断发生时,处理器会跳转到相应的中断处理程序执行
56. epoll在少事件时,epoll和select哪个好
事件数量较少的情况下,select和epoll性能差异不大,主要的开销来自系统调用本身,而不是处理大量文件描述符
(1)简单应用和可移植性
如果应用程序需要在多种Unix-like系统上运行,并且事件数量很少,可以优先选择select。它的实现和调试都相对简单,代码可移植性强
(2)特定于linux的应用
应用程序主要运行在linux上,及时事件数量较少,也可以选择epoll,为未来的扩展和优化做好准备
57. 什么是锁?什么是自旋锁和互斥锁?自旋锁和互斥锁的区别是什么?什么是读写锁?
(1)锁
锁是用于解决多线程和多进程环境中共享资源的并发访问和资源竞争问题。锁机制确保同时只有一个线程或进程能够访问共享资源,从而避免数据竞争和不一致性
(2)互斥锁(Mutex)(同步)
一种用于保护共享资源的同步机制。当一个线程持有互斥锁时,其他线程必须等待。直到该线程释放锁。互斥锁可以确保只有一个线程在同一时刻访问共享资源。
当一个线程尝试获取互斥锁(mutex)而该锁已被其他线程持有时,该线程通常会进入一种被称为“睡眠状态”(sleep state)或“阻塞状态”(blocked state)。这意味着该线程将暂停执行,直到它能够成功获取到互斥锁
补充:什么是睡眠状态?
睡眠状态是指线程在等待某个条件满足之前停止执行的状态。对于互斥锁而言,线程进入睡眠状态意味着它正在等待锁被释放。当锁释放时,操作系统会唤醒这些睡眠中的线程,让它们重新尝试获取锁。
(3)自旋锁(Spinlock)
自旋锁与互斥锁类似,但有一个关键的区别:当一个线程尝试获取自旋锁而该锁已经被其他线程持有时,该线程不会进入睡眠状态,而是会不断循环检查锁的状态,直到获取锁。由于自旋锁不会引起上下文切换,因此适用于锁持有事件非常短的场景。
(4)自旋锁和互斥锁的区别
1. 等待机制不同
(1)互斥锁:当一个线程无法获取锁时,它会被阻塞并进入睡眠状态,等待锁被释放
(2)自旋锁:当一个线程无法获取锁时,它会不断循环(自旋)检查锁的状态,直到获取锁
2. 应用场景不同
(1)互斥锁:适用于锁持有时间较长的场景,因为线程会被阻塞,避免了忙等待
(2)自旋锁:适用于锁持有时间非常短的场景,因为避免了上下文切换的开销
(5)读写锁
读写锁(rwlock)是一种允许多个线程同时读取共享资源,但在写入资源时独占访问的锁。读写锁提供了两种操作:读锁(读模式)和写锁(写模式)
1. 读锁
允许多个线程同时获取读锁,允许并发读取
2. 写锁
只能有一个线程写锁,获取写锁时其他读写锁都会被阻塞
58. 中断和信号的区别?什么是软中断和 硬中断?软中断和硬中断的区别?
(1)中断
中断是一种硬件或软件机制,用于中止当前进程或线程的执行,以响应外部事件或内部条件
当硬件设备需要与CPU通信时,它会通过中断请求线(IRQ)向CPU发送中断信号。CPU在接收到中断信号后,会暂停当前执行的指令序列,保存当前执行状态,然后转去执行相应的中断服务程序(ISR)。完成后,再恢复中断前的执行状态。
中断分为硬中断和软中断
(2)信号
信号是一种进程间通信机制,用于通知进程发生了异步事件(如异常、终止请求)
信号是一种软件层次的通知机制,当系统或其他进程发送信号给目标进程时,该进程的执行会被打断,来处理信号对应的处理程序,信号处理程序执行完后,进程可以选择继续执行原来的程序
(3)中断和信号的区别
中断主要是硬件触发和驱动的,用于处理设备或系统级别的事件,直接与CPU交互
信号则是软件层次的机制,更多用于进程间的通信或异常处理
(4)硬中断
硬中断是由硬件设备触发的中断,用于处理硬件事件
(5)软中断
软中断是由软件指令触发的中断,用于处理软件事件或系统调用
(6)硬中断和软中断的区别
1. 硬中断
由硬件设备触发,优先级高,响应速度快,主要处理硬件相关的事件
2. 软中断
由软件指令或操作系统生成,优先级低,响应速度慢,主要处理软件事件或系统调用