嵌入式面试题八股文(文件IO,网络编程,数据库)

一、IO进程:IPC

1.标准IO和系统IO的区别

标准文件IO概念:C库中定义的一组用于输入输出的函数      

特点

(1)有缓存机制,减少系统调用

(2)围绕文件流进行操作

(3)默认打开三个文件流,stdin、stdout、stderr

(4)只能对普通文件进行操作

系统文件IO概念:系统中的一组用于输入输出的函数

特点

(1)无缓存机制,每次都要系统调用

(2)围绕文件描述符进行操作

(3)默认打开三个文件描述符,0、1、2

(4)可以对任意类型的文件操作,不能操作目录  

标准IO

文件IO

概念

C库中定义的一组用于输入输出的函数

posix中定义的一组用于输入输出的函数

特点

  1. 有缓冲机制
  2. 围绕流进行操作,FILE *
  3. 默认打开三个流: stdin/stdout/stderr
  4. 只能操作普通类型文件
  5. 可移植性更强
  1. 没有缓冲机制
  2. 围绕文件描述符操作,非负整数
  3. 默认打开三个文件描述符:0/1/2
  4. 可以操作除了目录以外的任何类型文件
  5. 可移植性较弱

函数

打开文件:fopen、freopen

关闭文件:fclose

读文件:fgetc\fgets\fread

写文件:fputc\fputs\fwrite

定位操作:rewind\fseek\ftell

打开文件:open

关闭文件:close

读文件:read

写文件:write

定位操作:lseek

2.静态库和动态库的区别

静态库和动态库,本质区别是代码被载入时刻不同。

1) 静态库在程序编译时会被连接到目标代码中。

优点:程序运行时将不再需要该静态库,运行速度更快,移植性更好 ;

缺点:静态库中的代码复制到了程序中,因此体积较大;

静态库升级后,程序需要重新编译链接

2) 动态库是在程序运行被载入代码中。

优点:程序在执行时加载动态库,代码体积小;程序升级更简单;

不同应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。的地方肥瘦

缺点:运行时还需要动态库的存在,移植性较差

3.怎么创建进程

使用 fork() 函数来创建一个新的进程。fork() 函数会在当前进程的地址空间中创建一个新的进程,该新进程是当前进程的副本。新进程从 fork() 调用处开始执行,但它会拥有独立的进程 ID (PID)

#include <iostream>
#include <unistd.h> // 包含 fork 函数的头文
 
int main() {
    pid_t pid = fork(); // 调用 fork() 函数创建新进程

    if (pid == 0) {
        // 这里是子进程的代码
        std::cout << "Hello from child process! PID: " << getpid() << std::endl;
    } else if (pid > 0) {
        // 这里是父进程的代码
        std::cout << "Hello from parent process! Child PID: " << pid << std::endl;
    } else {
        // fork() 失败,无法创建新进程
        std::cerr << "Failed to create child process!" << std::endl;
    }

    return 0;
}

4.什么是守护进程

特点:守护进程是后台进程;生命周期比较长,从系统启动时开启,系统关闭时结束;它是脱离控制终端且周期执行的进程。

1) 创建子进程,父进程退出       

2) 在子进程中创建新会话

3) 改变进程运行路径为根目录

4) 重设文件权限掩码

5) 关闭其他不必要的文件描述符

5.什么是僵尸进程?什么是孤儿进程?                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          

若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程(要避免僵尸进程产生)。

解决方法:

  1. 使用wait()或waitpid()系统调用:父进程应该在子进程结束后及时调用wait()或waitpid()系统调用来回收子进程的资源。这样可以确保子进程的进程描述符被及时释放,从而避免僵尸进程的产生。
  2. 设置SIGCHLD信号的处理函数:父进程可以设置一个SIGCHLD信号的处理函数,当子进程结束时,该信号会被发送到父进程。在信号处理函数中,父进程可以调用wait()或waitpid()来回收子进程的资源。
  3. 忽略SIGCHLD信号:如果父进程不关心子进程的退出状态,可以选择忽略SIGCHLD信号。这样,当子进程结束时,内核会自动回收子进程的资源,不会产生僵尸进程。但需要注意的是,这种方法会使得父进程无法获取子进程的退出状态。

若父进程先结束,子进程成为孤儿进程,被init进程收养,会让子进程变成后台进程。

6.时间片了解么?

时间片是操作系统中用于调度进程的基本单位。在多道程序设计和多任务处理中,操作系统将 CPU 的执行时间划分成一小段小的时间片,每个进程在一个时间片内运行,然后切换到下一个进程。

时间片轮转算法是一种常见的调度算法,它将每个进程分配一个时间片,当时间片用完后,操作系统会暂停当前进程的执行,并将 CPU 分配给下一个就绪的进程,然后继续执行下一个时间片。这样,所有进程会依次获得 CPU 的执行机会,从用户的角度看,它们似乎是同时运行的。

时间片的大小通常由操作系统调度器决定,可以根据系统的性能、负载和策略来调整。如果时间片太小,会导致频繁的进程切换,增加系统开销;如果时间片太大,可能会导致长时间运行的进程占用 CPU 时间过长,导致其他进程响应变慢。

  1. 7.进程与线程的共性和区别?

共性

动态性:进程和线程都是动态的概念,它们的存在和消亡都是动态的,具有一定的生命周期。

并发性:进程和线程都可以并发执行,即多个进程或多个线程可以同时存在于内存中,并在操作系统的调度下交替执行。

独立性:进程和线程在各自的执行空间内是相对独立的,它们拥有各自的数据结构和堆栈空间,互不干扰。

资源分配:无论是进程还是线程,操作系统都会为它们分配必要的资源,如内存、文件、I/O设备等。

区别

资源开销:进程是资源分配的基本单位,它拥有独立的内存空间和系统资源,因此创建和销毁进程的开销相对较大。而线程是处理器调度的基本单位,它共享进程的资源,因此创建和销毁线程的开销较小。

执行过程:进程是独立执行的,它拥有一个完整的执行环境。而线程是在进程内部执行的,多个线程共享进程的内存空间和资源。

通信机制:进程间通信(IPC)需要操作系统提供特殊的机制,如管道、消息队列、共享内存等。而线程间通信则相对简单,因为它们共享进程的内存空间,可以直接读写共享变量或使用简单的同步机制。

稳定性:进程是操作系统分配资源的基本单位,因此进程崩溃通常不会影响到其他进程。而线程是进程的一部分,一个线程的崩溃可能导致整个进程的崩溃。

8.线程的同步 怎么实现

同步:在互斥的基础上按约定好的顺序对临界资源进行操作

 实现同步的机制:信号量、互斥锁+条件变量

include <stdio.h>
#include <pthread.h>

pthread_mutex_t mtx;       // 互斥锁
pthread_cond_t cond;       // 条件变量
int sharedResource = 0;    // 共享资源

void* producer(void* arg) {
    for (int i = 0; i < 5; i++) {
        pthread_mutex_lock(&mtx);
        sharedResource = i;/
        printf("Producer: Produced %d\n", i);
        pthread_cond_signal(&cond);  // 发送信号通知消费者
        pthread_mutex_unlock(&mtx);
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < 5; i++) {
        pthread_mutex_lock(&mtx);
        while (sharedResource < i) {
            // 等待条件满足
            pthread_cond_wait(&cond, &mtx);
        }
        printf("Consumer: Consumed %d\n", sharedResource);
        pthread_mutex_unlock(&mtx);
    }
    return NULL;
}

int main() {
    pthread_mutex_init(&mtx, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_t producerThread, consumerThread;

    pthread_create(&producerThread, NULL, producer, NULL);
    pthread_create(&consumerThread, NULL, consumer, NULL);

    pthread_join(producerThread, NULL);
    pthread_join(consumerThread, NULL);

    pthread_mutex_destroy(&mtx);
    pthread_cond_destroy(&cond);

    return 0;
}

9.线程的互斥怎么实现

互斥:同一时间只有一个线程对临界资源进行操作

 实现互斥的机制:互斥锁

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mtx; // 定义互斥锁

void* printNumber(void* arg) {
    int num = *(int*)arg;
    pthread_mutex_lock(&mtx); // 加锁,阻止其他线程访问共享资源
    printf("%d\n", num);
    pthread_mutex_unlock(&mtx); // 解锁,允许其他线程访问共享资源
    return NULL;
}

int main() {
    pthread_mutex_init(&mtx, NULL); // 初始化互斥锁

    pthread_t t1, t2;
    int num1 = 1, num2 = 2;

    pthread_create(&t1, NULL, printNumber, (void*)&num1);
    pthread_create(&t2, NULL, printNumber, (void*)&num2);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_mutex_destroy(&mtx); // 销毁互斥锁

    return 0;
}

10.进程间通信方式有哪些?哪种效率最高

无名管道、有名管道、信号量、共享内存、信号灯集、消息队列、套接字

效率最高是共享内存:

共享内存效率最高的主要原因是它避免了数据的复制和传输。在共享内存中,多个进程可以直接访问同一块物理内存,无需进行数据的拷贝和传输,因此在数据量较大的情况下,共享内存的效率要远高于其他进程间通信方式。

具体来说,以下是共享内存效率高的几个关键点:

避免数据拷贝:在其他进程间通信方式(如管道、消息队列等)中,数据需要从一个进程的地址空间复制到内核缓冲区,然后再从内核缓冲区复制到另一个进程的地址空间,涉及两次数据拷贝操作。而在共享内存中,多个进程共享同一块物理内存,不需要进行数据拷贝,直接读写共享内存即可。

高效的数据访问:共享内存的数据直接位于物理内存中,而其他进程间通信方式的数据位于内核缓冲区,因此在共享内存中访问数据更加高效。

无需内核介入:在共享内存中,数据的读写不需要内核的介入,减少了系统调用和上下文切换,提高了通信的速度和效率。

11.进程间通信方式的优缺点对比

共享内存适合用于大量数据的高效传输、

管道和消息队列适用于进程间的一对一通信、

套接字适合于网络通信

12.有名管道和无名管道的区别?

无名管道

具有亲缘关系的进程间

半双工通信方式,有固定的读端和写端,通过文件描述符操作

管道中没有数据时,读阻塞

管道中写满数据时,写阻塞

pipe(int fd[2])

文件IO中的read/write

有名管道

互不相关的两个进程间

在路径中存在管道文件,遵循先进先出,不支持lseek

只读方式打开,读阻塞

只写方式打开,写阻塞

mkfifo(“fifo”,0666);

文件IO的open/read/write/close

无名管道

有名管道

使用场景

具有亲缘关系的进程间

不相干的两个进程可以使用

特点

固定的读端fd[0]和写端fd[1]

可以看作一种特殊的文件

通过文件IO操作

在文件系统中存在管道文件,但是数据读写在内核空间中

通过文件IO进行操作

不支持lseek操作,先进先出

函数

pipe()

直接read\write

mkfifo()

先打开open(),再读写read\write

读写特性

当管道中无数据时读阻塞

当管道写满时写阻塞

当关闭读端向管道中写会管道破裂

只写方式打开阻塞直到另一个进程把读打开

只读方式打开阻塞直到另一个进程把写打开

可读可写打开,如果无数据读阻塞

13.共享内存的实现方式

创建共享内存:

首先,需要创建一块共享内存区域。在 Linux 系统中,可以使用 shmget 系统调用来创建或获取共享内存。需要提供一个唯一的键值来标识共享内存区域,并可以指定一些标志和权限来控制内存区域的属性。

映射共享内存:

一旦共享内存区域被创建,进程可以使用 sh mat 系统调用将共享内存区域关联到自己的地址空间中。需要提供共享内存的标识符,如果有多块共享内存区域,可以通过指定不同的标识符来关联不同的区域。关联后,进程就可以直接访问共享内存中的数据。

使用共享内存:

一旦共享内存区域被关联到进程的地址空间中,进程可以像访问普通内存一样直接读写共享内存中的数据。多个进程可以共享同一块物理内存,无需进行数据的拷贝和传输,提高了通信的效率。

解除关联:

当不再需要共享内存时,进程可以使用 shmdt 系统调用将共享内存区域从自己的地址空间中解除关联。解除关联后,进程将无法再访问共享内存中的数据。

删除共享内存:

当所有进程都解除了对共享内存的关联,可以使用 shmctl 系统调用删除共享内存区域,释放相关资源。需要注意,删除共享内存只是将共享内存标记为删除状态,实际的内存回收会在所有进程都解除关联后进行。  

14.消息队列的实现方式

(1)创建key值ftok

创建消息队列:

需要创建一个唯一的值key键来标识该消息队列,并可以指定一些标志来控制队列的属性。

使用系统调用(如 msgget)来创建一个消息队列。如果消息队列已经存在,可以通过指定相同的键值获取已有的消息队列。

发送消息到队列:

使用系统调用(如 msgsnd)将消息发送到队列中。需要指定消息队列的标识符、要发送的消息内容、消息的大小和一些标志。发送的消息需要封装成特定格式,包括消息类型和实际数据。

接收消息:

其他进程可以使用系统调用(如 msgrcv)从消息队列中接收消息。需要指定消息队列的标识符、用于接收消息的缓冲区、缓冲区的大小、要接收的消息类型和一些标志。如果队列中没有符合条件的消息,选择阻塞等

接收进程可以

或立即返回。

删除消息队列:

当不再需要消息队列时,可以使用系统调用(如 msgctl)将其删除,释放相关资源。删除消息队列前,需要确保所有进程都已经停止使用该队列。

15.fork和vfork区别

fork 和 vfork 是两种在 Linux/Unix系统中用于创建新进程的系统调用,它们有一些区别:

fork:

fork 是创建新进程的标准方法,它会复制当前进程的所有资源(包括代码、数据、堆栈等),创建一个与父进程几乎完全相同的子进程。

父进程和子进程之间的地址空间是独立的,互相不会影响。子进程从 fork 调用之后的位置开始执行,可以通过返回值来判断当前进程是父进程还是子进程。

在 fork 调用后,父进程和子进程并发执行,并共享一些资源,比如打开的文件描述符、信号处理器等。

父进程和子进程的执行顺序是不确定的,取决于系统调度器的调度策略。

vfork:

vfork 也是用于创建新进程的系统调用,但它与 fork 有一些不同之处。

vfork 创建的子进程与父进程共享地址空间,即子进程与父进程共用同一份数据和代码。这意味着在子进程中执行的任何修改都会直接影响到父进程,因此 vfork 的使用要非常小心。

vfork 是为了在子进程中立即执行一个新程序(通常是通过 exec 系统调用),然后立即退出,不需要复制父进程的地址空间,以节省资源。

在 vfork 调用后,父进程会被阻塞,直到子进程执行完毕或调用 exec 系统调用后才会继续执行。

总的来说,fork 是创建新进程的常规方法,而 vfork 适用于在子进程中立即执行一个新程序。在使用 vfork 时需要格外小心,避免对父进程的地址空间造成破坏。通常情况推荐使用 fork 来创建新进程。

16.线程的死锁,怎么避免?

死锁:

是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成 的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

死锁产生的四个必要条件

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

  1. 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放

3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。  

注意:当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

解决方法

1)避免循环等待:确保线程获取资源的顺序是一致的,避免形成循环等待的情况

(2)使用资源分级:将资源分为不同的等级或优先级,确保线程按照一定的顺序获取资源

(3)使用超时机制:在获取资源时,设置一个超时时间,并在超过时间限制后放弃获取资源并释放已持有的资源

(4)避免资源的重复占用:尽量避免在一个线程中重复获取已经持有的资源

(5)合理规划资源的利用

(6)使用死锁检测和恢复机制

二、网络编程

1. TCP与UDP的异同点

1.共同点:

 同为传输层协议

  1. 不同点:

 TCP:有连接,可靠:无误、数据无丢失、数据无失序、数据无重复到达

 UDP:无连接,不保证可靠

3.应用场合:

 TCP:传输质量较高,如qq登陆操作

 UDP:对传输质量要求不高,但要保证效率,如qq聊天、语聊、视频。广播和组播

2. TCP的三次握手过程

三次握手过程:

第一次握手:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。  

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

3. TCP的四次挥手过程

四次挥手过程:

第一次挥手:某个应用进程首先调用close,我们称这一端执行主动关闭。于是这一端的TCP发送一个FIN分节,表示数据发送完毕。

第二次挥手:接收到FIN的另一端执行被动关闭(passive close)。这个FIN由TCP确认。它的接收也作为文件结束符传递给接收端应用进程(放在已排队等候应用进程接收到任何其他数据之后)

第三次挥手:一段时间后,接收到文件结束符的应用进程将调用close关闭它的套接口。这导致它的TCP也发送一个FIN。

第四次挥手:接收到这个FIN的原发送端TCP对它进行确认。

面向字节的数据传送流(如TCP字节流、Unix管道等)也使用EOF表示在某个方向上不再有数据待传送。在TCP字节流中,EOF的读或写通过收发一个特殊的FIN分节来实现

 4. TCP是如何保证可靠传输的

校验:TCP 将保持它首部和数据的检验和这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到端的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。

序列号:TCP 传输时将每个字节的数据都进行了编号,这就是序列号。(为了应对延时抵达和排序混乱)。每个连接都会选择一个初始序列号,初始序列号(视为一个 32 位计数器),会随时间而改变(每 4 微秒加 1)。因此,每一个连接都拥有不同的序列号。序列号的作用不仅仅是应]答的作用,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据。这也是 TCP 传输可靠性的保证之一。

确认应答:TCP 传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答。也就是发送 ACK 报文。这个 ACK 报文当中带有对应的确认序列号,告诉发送方,接收到了哪些数据,下一次的数据从哪里发。

超时重传:超时重传机制。简单理解就是发送方在发送完数据后等待一个时间,时间到达没有接收到 ACK 报文,那么对刚才发送的数据进行重新发送。如果是刚才第一个原因,接收方收到二次重发的数据后,便进行 ACK 应答。如果是第二个原因,接收方发现接收的数据已存在(判断存在的根据就是序列号,所以上面说序列号还有去除重复数据的作用),那么直接丢弃,仍旧发送 ACK 应答。那么发送方发送完毕后等待的时间i是多少呢?如果这个等待的时间过长,那么会影响 TCP 传输的整体效率,如果等待时间过短,又会导致频繁的发送重复的包。如何权衡?由于 TCP 传输时保证能够在任何环境下都有一个高性能的通信,因此这个最大超时时间(也就是等待的时间)是动态计算的。  

连接管理:说白了就是三次握手四次挥手。

流量控制:当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。

拥塞控制:拥塞控制是 TCP 在传输时尽可能快的将数据传输,并且避免拥塞造成的一系列问题。是可靠性的保证,同时也是维护了传输的高效性。

5. 如何实现并发服务器

多线程并发服务器:

使用一个主线程监听服务器端口,当有客户端连接请求时,主线程接受连接并创建一个新的线程来处理该连接。

每个线程负责与一个客户端进行通信,接收客户端发送的数据并进行相应的处理,然后将处理结果发送给客户端。

多个线程之间共享服务器资源,如共享的数据结构、文件描述符等。需要使用互斥锁等同步机制来保证线程间的数据安全。

多进程并发服务器

使用一个主进程监听服务器端口,当有客户端连接请求时,主进程接受连接并创建一个新的子进程来处理该连接。   

每个子进程负责与指定一个客户端进行通信,接收客户端发送的数据并进行相应的处理,然后将处理结果发送给客户端。

子进程之间独立运行,拥有各自的地址空间,不会相互影响。

多个进程之间不共享资源,需要使用进程间通信(IPC)机制来传递数据,如管道、消息队列、共享内存等。

多进程:   

优点:  服务器更稳定 , 父子进程资源独立,  安全性高一点

缺点:  需要开辟多个进程,大量消耗资源

多线程:

优点:   相对多进程, 资源开销小,  线程共享同一个进程的资源

缺点:   需要开辟多个线程,而且线程安全性较差

IO多路复用:

优点:  节省资源, 减小系统开销,性能高;

缺点:  代码复杂性高

6. select、poll、epoll的区别

select:

1. 一个进程最多只能监听1024个文件描述符 (千级别)

  1. select被唤醒之后需要重新轮询一遍,效率比较低(消耗CPU资源);

3. select每次会清空表,每次都需要拷贝用户空间的表到内核空间,效率低(一个进行0~4G,0~3G是用户态,3G~4G是内核态,拷贝是非常耗时的);  

poll:

1. 优化文件描述符个数的限制;(根据poll函数第一个函数的参数来定,如果监听的事件为1个,则结构体数组的大小为1,如果想监听100个,那么这个结构体数组的大小就为100,由程序员自己来决定)

2. poll被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低

3. poll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可

epoll:(本世纪最好用的io多路复用机制)

1. 监听的最大的文件描述符没有个数限制(理论上,取决与你自己的系统)

2. epoll当有事件产生被唤醒之后,文件描述符主动调用callback函数直接拿到唤醒的文件描述符,将文件描述符拷贝到用户空间即可

  1. epoll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可

select

poll

epoll

监听个数

一个进程最多监听1024个文件描述符

由程序员自己决定

百万级

方式

每次都会被唤醒,都需要重新轮询

每次都会被唤醒,都需要重新轮询

红黑树内callback自动回调,不需要轮询

效率

文件描述符数目越多,轮询越多,效率越低

文件描述符数目越多,轮询越多,效率越低

不轮询,效率高

原理

每次使用select后,都会清空表

每次调用select,都需要拷贝用户空间的表到内核空间

内核空间负责轮询监视表内的文件描述符,将发生事件的文件描述符拷贝到用户空间,再次调用select,如此循环

不会清空结构体数组

每次调用poll,都需要拷贝用户空间的结构体到内核空间

内核空间负责轮询监视结构体数组内的文件描述符,将发生事件的文件描述符拷贝到用户空间,再次调用poll,如此循环

不会清空表

epoll中每个fd只会从用户空间到内核空间只拷贝一次(epoll_ctl添加ADD时)

通过epoll_ctl将文件描述符交给内核监管,一旦fd就绪,内核就会采用callback的回调机制来激活该fd,epoll_wait便可以收到通知(内核空间到用户空间的拷贝

特点

  1. 一个进程最多能监听1024个文件描述符
  2. select每次被唤醒,都要重新轮询表,效率低
  3. select每次都清空未发生相应的文件描述符,每次都要拷贝用户空间的表到内核空间
  1. 优化文件描述符的个数限制
  2. poll每次被唤醒,都要重新轮询,效率比较低(耗费cpu)
  3. poll不需要构造文件描述符表(也不需要清空表),采用结构体数组,每次也需要从用户空间拷贝到内核空间
  1. 监听的文件描述符没有个数限制(取决于自己的系统)
  2. 异步IO,epoll当有事件产生被唤醒,文件描述符会主动调用callback函数拿到唤醒的文件描述符,不需要轮询,效率高
  3. epoll不需要构造文件描述符的表,只需要从用户空间拷贝到内核空间一次。

结构

文件描述符表(位表)

结构体数组

红黑树和就绪链表

开发复杂度

7. Tcp的粘包问题

TCP的粘包问题是指在传输过程中,由于TCP协议的特性,多个数据包可能会被合并成一个大的数据包,或者一个数据包被拆分成多个小的数据包,从而导致接收端无法正确解析和处理数据。

造成TCP粘包问题的主要原因是TCP协议是面向流的,数据流在发送端和接收端之间是连续不断的,而没有消息边界的概念。因此,多个数据包在传输过程中可能会被合并成一个大的数据流,或者一个数据包被拆分成多个小的数据流。

解决TCP粘包问题的常见方法有以下几种:  

固定长度消息:

发送端将每个数据包固定长度发送,接收端按照固定长度来接收和解析数据。这种方法简单直接,但可能会造成数据浪费,因为某些数据包可能会很大,而其他数据包可能会很小。

消息边界标志:

发送端在每个数据包的末尾添加特定的标志来表示消息的结束,接收端通过检测标志来识别消息的边界。例如,可以在每个数据包的末尾添加换行符或其他特定字符作为消息边界。

消息长度字段:

发送端在每个数据包的头部添加一个表示消息长度的字段,接收端先读取消息长度字段,然后根据消息长度来接收和解析数据。这种方法比固定长度消息更灵活,但需要处理消息长度字段可能被拆分的情况。

使用专门的消息协议:

可以定义自己的消息协议,在消息中包含消息长度字段、消息类型等信息,使得接收端可以正确解析和处理数据。

应用层处理:

在应用层进行数据的拼接和解析,将多个小数据包合并成一个完整的消息。

8. UDP的丢包问题

UDP的丢包问题是指在UDP协议传输过程中,由于网络原因或接收端处理能力不足,部分数据包可能在传输过程中丢失,导致接收端无法收到完整的数据。

UDP是一种无连接、不可靠的传输协议,它不提供可靠性保证,不保证数据包的顺序和可靠传输。因此,UDP的丢包问题是很常见的情况,特别是在网络状况较差或网络拥堵的情况下更容易发生。

造成UDP丢包问题的主要原因包括:  

  1. 网络拥堵:当网络中的数据流量超过网络设备处理能力时,会导致数据包丢失。
  2. 网络延迟:网络传输的延迟会导致数据包到达接收端的时间不确定,从而可能造成数据包的丢失。
  3. 缓冲区溢出:接收端的缓冲区大小有限,如果数据到达速度过快,缓冲区可能会溢出,导致部分数据包丢失。
  4. 数据包大小:UDP本身没有数据包大小限制,但在网络传输中,数据包过大可能会)被分片,而分片可能会造成部分数据包丢失。

要解决UDP的丢包问题,可以考虑以下方法:

  1. 重传机制:在应用层实现重传机制,发送端定时重发未确认的数据包,确保接收端收到完整的数据。
  2. 数据包校验:可以在应用层添加数据包序号和校验码等信息,接收端通过校验和来检查数据的完整性,丢弃校验和错误的数据包。
  3. 网络优化:优化网络拓扑和带宽,减少网络延迟和拥堵,提高数据传输的稳定性。
  4. 应用层处理:在应用层对数据进行分段、拼接和校验,确保数据的完整性。

9. TCP编程框架

服务器端:

socket()函数创建流式套接字用于建立连接

bind()函数绑定服务器ip和端口号

listen()函数改变套接字属性化主动为被动

accept()函数等待连接产生用于通信的套接字

send()/recv()函数读写

close()关闭文件描述符  

客户端:

socket()函数创建流式套接字用于通信

connect()函数建立与服务器的连接

send()/recv()函数读写

close函数关闭文件描述符

  1. 用过抓包工具么?抓过什么数据包?

Wireshark

 Wireshark过滤规则及使用方法-CSDN博客

tcpdump

https://www.cnblogs.com/jiujuan/p/9017495.html

12. 广播和组播的区别

一、主体不同*

  1. 组播:为了减少在广播中涉及的不必要的开销,可以只向特定的一部分接收方(可以是域内也可以是域间)发送流量。

2、广播:将报文发送到网络中的所有可能的接收者。

二、原理不同

  1. 组播:在通信中参与或感兴趣的主机需要处理相关的分组,流量只会 被承载于它将被使用的链路上,并且只有任意组播数据报的一个副本被承载于这样的链路上。

2、广播:路由器简单地将它接收到的任何广播报文副本转发到除该报文到达的接口以外的每个接口。

三、特点不同

1、组播:通过发送方指明接收方,或是通过接收方独立地指明就可以完。成这项工作。然后网络只负责向预期的或感兴趣的收件方发送流量。

2、广播:通过将地址中的主机部分全部置1形成的,特殊地址255.255.255.255对应于本地网络广播。

12. Tcp/Ip网络模型分为几层?每一层什么作用?都有哪些协议?

TCP/IP网络模型分为四层,每一层的作用和常见协议如下:

应用层(Application Layer):

应用协议和应用程序的集合

常见协议:HTTP、FTP、SMTP、DNS、Telnet、SSH、SNMP等。

传输层(Transport Layer):

端到端,决定数据交给机器的哪个任务(进程)去处理,通过端口寻址。

常见协议:TCP(传输控制协议)、UDP(用户数据报协议)。

网络层(Internet Layer):

提供设备对设备的传输,可以理解为通过IP寻址机器。

常见协议:IP、ICMP、IGMP。

网络接口和物理层Network interfaces and physical layers):

屏蔽硬件差异(驱动),向上层提供统一的操作接口。

常见协议:Ethernet、(Point-to-Point Protocol)、ARP(Address Resolution Protocol)、RARP(Reverse Address Resolution Protocol)。

13. OSI模型?

物理层(Physical Layer):

物理层负责传输比特流,主要定义物理介质和电气特性,以及数据传输的硬件细节。

数据链路层(Data Link Layer):

数据链路层负责将数据包划分成帧,并处理帧的传输和接收。

传输层(Transport Layer):

传输层提供端到端的数据传输服务,确保数据的可靠传输和错误恢复。

提供端口号、流量控制和数据重传等功能,常见的传输层协议有TCP和UDP。

会话层(Session Layer):

会话层负责建立、管理和终止会话(通信连接)。

管理不同设备之间的对话,确保数据传输的顺序和同步。

表示层(Presentation Layer)

表示层负责数据的格式化和转换,确保数据在不同系统中的表示和解释相互兼容。

对数据进行加密、压缩、编码和解码等处理。

应用层(Application Layer):

应用层提供网络应用程序和用户之间的接口,负责应用程序的通信。

各种网络应用,如电子邮件、文件传输、远程登录等都在应用层实现。  

14. 学过什么数据库?增删改查语句?

MySQL和SQLite

  1. 常用的数据库

大型数据库 :Oracle                                     

中型数据库 :Server是微软开发的数据库产品,主要支持windows平台

小型数据库 : mySQL是一个小型关系型数据库管理系统。开放源码 (嵌入式不需要存储太多数据)

  1. 增删改查:

SQL基础语句基本使用

15. MySQL和SQLite区别

嵌入式数据库:

MySQL是典型的客户端-服务器架构数据库,需要通过网络连接来访问数据库。它支持多用户、多线程的并发操作,适用于大型应用和高并发场景。

SQLite是一种嵌入式数据库,直接嵌入到应用程序中,不需要独立的服务器进程。它适用于轻量级应用和单用户环境。

数据库大小和性能:

MySQL适用于大型数据库和高性能需求,支持复杂的查询和大规模的数据存储。

SQLite适用于小型数据库和低资源环境,它的存储引擎简单高效,适合移动设备和嵌入式系统。

数据库特性:

MySQL持支完整的SQL语法,包括事务处理、视图、存储过程、触发器等高级特性。

SQLite支持大部分SQL语法,但不支持存储过程和触发器等高级特性。

部署和管理:

MySQL需要单独部署和配置数据库服务器,需要额外的管理和维护工作。

SQLite作为嵌入式数据库,不需要独立的服务器,部署和管理比较简单。

存储引擎:

MySQL支持多种存储引擎,如InnoDB、M0yISAM等,可以根据需求选择不同的存储引擎。

SQLite只有一个存储引擎,但它在大部分场景下性能较好。

  1. 网络编程中做的项目,在简历中写的重点会问到
  2. 四种IO
  1. 阻塞IO:最常见、效率低、不浪费cpu
  2. 非阻塞式IO:轮询、耗费CPU、可以处理多路IO
  3. 信号驱动IO/异步IO:异步通知方式,需要底层驱动的支持
  4. IO多路复用:可以同时处理多路IO,不需要轮询,效率高
嵌入式开发面试题通常包括以下几个方面的内容: 1. 嵌入式系统基础知识:包括什么是嵌入式系统、嵌入式系统的特点、嵌入式系统的应用等。 2. C/C++编程语言:包括基本语法、数据类型、指针、内存管理、函数、结构体、文件操作等。 3. 操作系统:包括实时操作系统(RTOS)的原理、任务调度、进程间通信、内存管理等。 4. 单片机/微处理器:包括常见单片机/微处理器的特点、寄存器、外设接口、中断处理等。 5. 通信协议:包括串口通信、SPI、I2C、CAN总线等常用的通信协议。 6. 电路设计:包括数字电路设计、模拟电路设计、常用传感器的接口等。 7. 嵌入式系统调试与测试:包括调试工具的使用、代码调试技巧、性能优化等。 8. 项目经验:展示自己在嵌入式开发领域的项目经验,包括项目的整体架构、关键技术、遇到的问题以及解决方案等。 9. 算法与数据结构:包括常见的排序算法、查找算法、链表、树等。 根据你提供的引用内容,可能在面试中会问到关于进程和线程的问题。进程是计算机中正在运行的程序的实例,而线程是进程中的一条执行路径。在嵌入式开发中,进程与线程的概念同样适用。你可能会被问到进程和线程的区别、进程间通信的方式、线程同步与互斥等问题。 记住,除了准备好上述的知识点和问题,还可以通过阅读相关的书籍和参加一些实际的嵌入式开发项目来提高自己的面试准备。祝你成功!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值