相遇皆是缘分

进程和线程

线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
1.创建线程
pthread_t tid; //线程类型
pthread_create(&tid, NULL, working, NULL); //线程创建函数 working:指定一个处理函数

thread(幅 ruai 的)
create(亏 A t)

2. 线程退出
pthread_exit //只要调用该函数当前线程就马上退出了,并且不会影响到其他线程的正常运行,不管是在子线程或者主线程中都可以使用。

exit(爱 克 谁 t)

3.线程回收
/*这个函数是一个阻塞函数,如果还有子线程在运行,调用该函数就会阻塞,子线程退出函数解除阻塞进行资源的回收,函数被调用一次,只能回收一个子线程,如果有多个子线程则需要循环进行回收。*/
pthread_join

4. 线程分离
pthread_detach //线程就可以和主线程分离,当子线程退出的时候,其占用的内核资源就被系统的其他进程接管并回收了

detach(滴他气)

5. 线程取消
线程取消的意思就是在某些特定情况下在一个线程中杀死另一个线程。使用这个函数杀死一个线程需要分两步:
在线程A中调用线程取消函数pthread_cancel,指定杀死线程B,这时候线程B是死不了的
在线程B中进行一次系统调用(从用户区切换到内核区),如何调用?比如说: 用 pthread_self 会系统调用

pthread_cancel
cancel( 康 受 )

6. 返回当前线程的线程ID
pthread_self
self()

互斥锁

互斥锁可以锁定一个代码块,被锁定的这个代码块,所有的线程只能顺序执行(不能并行处理),这样多线程访问共享资源数据混乱的问题就可以被解决了,需要付出的代价就是执行效率的降低,因为默认临界区多个线程是可以并行处理的,现在只能串行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
互斥锁的类型为pthread_mutex_t

// 初始化互斥锁
pthread_mutex_init()
// 释放互斥锁资源
pthread_mutex_destroy()

thread(幅 ruai 的)
mutex(miu 太 克 s)
destroy(抵 死 作 以 )

1.创建互斥锁
pthread_mutex_t mutex;


2.上锁阻塞,解锁后解除阻塞
pthread_mutex_lock(&mutex);

....

3.解锁
pthread_mutex_unlock(&mutex);
unlock(昂 洛克)



死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
所有的线程都被阻塞,并且线程的阻塞是无法解开的(因为可以解锁的线程也被阻塞了)。

造成原因:
1.加锁之后忘记解锁
2.重复加锁, 造成死锁
3.在程序中有多个共享资源, 因此有很多把锁,随意加锁,导致相互被阻塞

避免方法:
1.避免多次锁定, 多检查
2.对共享资源访问完毕之后, 一定要解锁,或者在加锁的使用 trylock
3.如果程序中有多把锁, 可以控制对锁的访问顺序(顺序访问共享资源,但在有些情况下是做不到的),另外也可以在对其他互斥锁做加锁操作之前,先释放当前线程拥有的互斥锁。
4.项目程序中可以引入一些专门用于死锁检测的模块


读写锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 初始化读写锁
int pthread_rwlock_init();4
// 释放读写锁占用的系统资源
int pthread_rwlock_destroy();

thread(幅 ruai 的)
destroy(抵 死 作 以 )

1.创建读写锁
pthread_rwlock_t rwlock;

2.读/写
pthread_rwlock_rdlock(&rwlock);
pthread_rwlock_wdlock(&rwlock);

3.解锁
pthread_rwlock_unlock(&rwlock);
unlock(昂 洛克)

因为通过一把读写锁可以锁定读或者写操作,下面介绍一下关于读写锁的特点:
1.使用读写锁的读锁锁定了临界区,线程对临界区的访问是并行的,读锁是共享的。
2.使用读写锁的写锁锁定了临界区,线程对临界区的访问是串行的,写锁是独占的。
3.使用读写锁分别对两个临界区加了读锁和写锁,两个线程要同时访问者两个临界区,访问写锁临界区的线程继续运行,访问读锁临界区的线程阻塞,因为写锁比读锁的优先级高。

条件变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
条件变量的主要作用是进行线程的阻塞。一般情况下条件变量用于处理生产者和消费者模型,并且和互斥锁配合使用

1.创建条件变量
pthread_cond_t cond;


// 初始化
int pthread_cond_init();
// 销毁释放资源
int pthread_cond_destroy();

destroy(抵 死 作 以 )

2.线程阻塞函数, 哪个线程调用这个函数, 哪个线程就会被阻塞
int pthread_cond_wait()

信号量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,信号量和条件变量一样用于处理生产者和消费者模型,用于阻塞生产者线程或者消费者线程的运行。
*
* 资源总数为1,不需要琐,不需要线程同步
* 资源总数>1时,需要琐,需要线程同步
*
// 初始化信号量/信号灯
int sem_init();
// 资源释放, 线程销毁之后调用这个函数即可
int sem_destroy();

sem(塞母)
destroy(抵 死 作 以 )

1.创建信号量
sem_t sem;

2.资源就会被消耗1个, 资源数-1 (消费者)
int sem_wait(sem_t *sem);


3.资源数+1 (生产者)
int sem_post(sem_t *sem);

进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1.当前进程的进程ID(PID)
pid_t getpid(void);

2.获取当前进程的父进程 ID(PPID)
pid_t getppid(void);

3.创建一个新的进程
pid_t fork(void);

父子进程
父进程从main()函数开始 就是父进程
子各种从fork()函数之后被创建, 子进程就从fork()之后开始向下执行代码

exec族函数中最常用的有两个execl()和execlp()
调用这些函数的时候都会先创建一个子进程,在子进程中调用 exec 族函数,子进程的用户区数据被替换掉开始执行新的程序中的代码逻辑,但是父进程不受任何影响仍然可以继续正常工作。
execl() //该函数可用于执行任意一个可执行程序,函数需要通过指定的文件路径才能找到这个可执行程序
execlp() //常用于执行已经设置了环境变量的可执行程序

execl(以克 Z 要)

管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建一个匿名的管道, 得到两个可用的文件描述符
int pipe(int pipefd[2]);

pipe(派普)

// 读管道
ssize_t read(int fd, void *buf, size_t count);
// 写管道的函数
ssize_t write(int fd, const void *buf, size_t count);

管道的是进程间通信(IPC-InterProcess Communication)的一种方式,管道的本质其实就是内核中的一块内存(或者叫内核缓冲区),这块缓冲区中的数据存储在一个环形队列中,因为管道在内核里边,因此我们不能直接对其进行任何操作。
其实管道操作就是文件IO操作,通过文件IO的读(read)/写(write)来操作管道的读写


消息队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//获取唯一键值
key_t key = ftok("/",2023);

ftok(服拓克)

//通过key值生成消息队列
msgget(key, IPC_CREAT | 0666 );

msgget(卖给特)

//编辑消息队列信息
MSG _msg;
memset(&msg,0,sizeof(msg));

memset(闷谁t)

//接受消息
msgrcv()
msgrcv(m s 鸡 k)

.消息队列是消息的链表,存放于内存中,内核维护消息队列
管道比较适用于较少进程之间的通信,那么如果想要实现多个进程之前的通信的话,我们需要使用消息队列

内存映射

1
2
3
4
5
6
7
8
// 创建内存映射区
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
//释放
int munmap(void *addr, size_t length);

munmap(闷卖铺)

如果想要实现进程间通信,可以通过函数创建一块内存映射区,和管道不同的是管道对应的内存空间在内核中,而内存映射区对应的内存空间在进程的用户区(用于加载动态库的那个区域),也就是说进程间通信使用的内存映射区不是一块,而是在每个进程内部都有一块。

共享内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
共享内存不同于内存映射区,它不属于任何进程,并且不受进程生命周期的影响。通过调用Linux提供的系统函数就可得到这块共享内存。使用之前需要让进程和共享内存进行关联,得到共享内存的起始地址之后就可以直接进行读写操作了,进程也可以和这块共享内存解除关联,解除关联之后就不能操作这块共享内存了。在所有进程间通信的方式中共享内存的效率是最高的。
共享内存操作默认不阻塞,如果多个进程同时读写共享内存,可能出现数据混乱,共享内存需要借助其他机制来保证进程间的数据同步,比如:信号量,共享内存内部没有提供这种机制。

// 将两个参数作为种子, 生成一个 key_t 类型的数值
key_t ftok(const char *pathname, int proj_id);

1.创建共享内存
shmget(key_t key, size_t size, int shmflg);

shmget(西给t)

2.关联
*shmat()

3.解除
shmdt

4.删除共享内存
shmctl

相关shell命令
使用ipcs 添加参数-m可以查看系统中共享内存的详细信息


进程间通信
1. 调用linux的系统API创建一块共享内存
- 这块内存不属于任何进程, 默认进程不能对其进行操作

2. 准备好进程A, 和进程B, 这两个进程需要和创建的共享内存进行关联
- 关联操作: 调用linux的 api
- 关联成功之后, 得到了这块共享内存的起始地址

3. 在进程A或者进程B中对共享内存进行读写操作
- 读内存: printf() 等;
- 写内存: memcpy() 等;

4. 通信完成, 可以让进程A和B和共享内存解除关联
- 解除成功, 进程A和B不能再操作共享内存了
- 共享内存不受进程生命周期的影响的

5. 共享内存不在使用之后, 将其删除
- 调用linux的api函数, 删除之后这块内存被内核回收了


shm和mmap的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
共享内存和内存映射区都可以实现进程间通信,下面来分析一下二者的区别:

实现进程间通信的方式

shm: 多个进程只需要一块共享内存就够了,共享内存不属于进程,需要和进程关联才能使用
内存映射区: 位于每个进程的虚拟地址空间中, 并且需要关联同一个磁盘文件才能实现进程间数据通信
效率:

shm: 直接对内存操作,效率高
内存映射区: 需要内存和文件之间的数据同步,效率低
生命周期

内存映射区:进程退出, 内存映射区也就没有了
shm:进程退出对共享内存没有影响,调用相关函数/命令/ 关机才能删除共享内存
数据的完整性 -> 突发状态下数据能不能被保存下来(比如: 突然断电)

内存映射区:可以完整的保存数据, 内存映射区数据会同步到磁盘文件
shm:数据存储在物理内存中, 断电之后系统关闭, 内存数据也就丢失了

信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Linux中的信号有三种状态,分别为:产生,未决,递达。

1.相关的信号给到对应的进程。
kill/raise/abort

2. 定时器
alarm 只能进行单次定时,定时完成发射出一个信号。
setitimer 可以进行周期性定时,每触发一次定时器就会发射出一个信号。

3.信号集
在PCB中有两个非常重要的信号集。一个称之为“阻塞信号集”,另一个称之为“未决信号集”。
信号的“未决”是一种状态,指的是从信号的产生到信号被处理前的这一段时间。
信号的“阻塞”是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

4.信号捕捉
signal
sigaction

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
目标文件:依赖文件
要执行的命令

test : main.o sub.o
gcc -o test main.o sub.o

//要生成test可执行文件的话,先检查main.o和shu.o是否有更新,有更新就执行 gcc -o test main.o sub.o 命令,生成test文件

%.o : %.c
gcc -c -o $@ $<
clean:
rm *.o test -f

%.o:表示所用的.o文件
%.c:表示所有的.c文件
$@:表示目标
$<:表示第1个依赖文件
$^:表示所有依赖文件

什么是进程﹖什么是线程?

1
进程是资源(CPU、内存等)分配的基本单位,线程是CPU调度和分配的基本单位(程序执行的最小单位)。

请问进程和线程有什么区别?

1
2
我理解的进程它其实就是一个APP吧
就比如说我们在电脑上打开任务管理器,然后它里面就会有很多的一个进程列表,比如说一个QQ它就是一个进程比如说一个英雄联盟他就是一个进程,进程他是我们操作系统资源分配的一个基本单位,在一个进程里可以拥有一个或者多个线程,当一个进程里面只有一个线程,我就理解为他们没什么区别,如果一个进程里面有多个线程就不一样了,多个线程它其实是共享进程的一个资源的,然后这就导致了在同一个进程里面某一个线程挂了就有可能会导致其他的线程也会出问题,但是每一个进程之间是相互独立的 线程是CPU调度的一个基本单位

线程的作用

1
2
3
1.CPU调度和分配的基本单位(能独立运行的基本单位)
2.可并发执行:在一个进程中的多个线程之间可以并发执行,甚至允许在一个进程中所有线程都能并发执行
3.共享进程资源:在同一进程中的各个线程,都可以共享该进程所拥有的资源

何时使用多进程,何时使用多线程?

1
2
3
安全稳定选进程 , 快速频繁选线程
对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。

进程、线程间通信方式有哪些?有什么优缺点?

进程

线程

1
2
3
4
5
信号量
读写锁
条件变量
互斥锁
自旋锁

互斥锁与信号量的区别?

互斥锁用于线程的互斥,信号量用于线程的同步。
这是互斥锁和信号量的根本区别,也就是互斥和同步之间的区别。
同时互斥锁的作用域仅仅在于线程,信号量可以作用于线程和进程。

进程有几种状态?

自旋锁 和 信号量 区别

1
2
3
4
自旋锁,顾名思义,我们可以把他理解成厕所门上的一把锁。这个厕所门只有一把钥匙,当我们进去时,把钥匙取下来,进去后反锁。那么当第二个人想进来,必须等我们出去后才可以。当第二个人在外面等待时,可能会一直等待在门口转圈。

比如,厕所一次只能让一个人进去,当A在里面的时候,B想进去,如果是自旋锁,那么B就会一直在门口傻傻等待。如果是信号量,A就会给B一个信号,你先回去吧,我出来了叫你。这就是一个信号量的例子,B听到A发出的信号后,可以先回去睡觉,等待A出来。

进程上下文和中断上下文

进程上下文

1
2
进程上下文,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容,当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。

中断上下文

1
当由硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理,中断上下文就可以理解为硬件传递过来的这些参数和内核需要保存的一些环境,主要是被中断的进程的环境。

父进程、子进程的关系以及区别

1
2
子进程从父进程继承的主要有:用户号和用户组号;堆栈;共享内存;目录(当前目录、根目录)﹔打开文件的描述符;
但父进程和子进程拥有独立的地址空间和PID参数、不同的父进程号、自己的文件描述符。

同步和异步和互斥

1
2
3
4
5
线程同步:每个线程之间按预定的先后次序进行运行,协同、协助、互相配合。
可以理解成"你说完,我再做”。有了线程同步,每个线程才不是自己做自己的事情,而是协同完成某件大事。

线程互斥:当有若干个线程访问同一块资源时,规定同一时间只有一个线可以得到访问权,其它线程需要等占用资源者释放该资源才可以申请访问。
线程互斥可以看成是一种特殊的线程同步。

孤儿进程、僵尸进程、守护进程

1
2
3
孤儿进程:当父进程退出后它的子进程还在运行,那么这些子进程就是孤儿进程。孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作。
僵尸进程:当子进程退出后而父进程并未接收结束子进程(如调用waitpid获取子进程的状态信息),那么子进程仍停留在系统中,这就是僵尸进程。
守护进程:是在后台运行不受终端控制的进程(如输入、输出等)。网络服务大部分就是守护进程。

僵尸进程的危害,和解决的方法

1
2
3
4
5
6
7
8
危害:
进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。

解决:
系统规定,子进程退出后,父进程会自动收到SIGCHLD信号。因此我们需要在父进程里重置signal函数。每当子进程退出,父进程都会收到SIGCHLD信号,故通过signal函数,重置信号响应函数。

SIGCHLD(塞切的)sigchld
signal(塞格no)

线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
线程池的组成主要分为3个部分,这三部分配合工作就可以得到一个完整的线程池:

1.任务队列(生产者),存储需要处理的任务,由工作的线程来处理这些任务
通过线程池提供的API函数,将一个待处理的任务添加到任务队列,或者从任务队列中删除
已处理的任务会被从任务队列中删除
线程池的使用者,也就是调用线程池函数往任务队列中添加任务的线程就是生产者线程
2.工作的线程(任务队列任务的消费者) ,N个
线程池中维护了一定数量的工作线程, 他们的作用是是不停的读任务队列, 从里边取出任务并处理
工作的线程相当于是任务队列的消费者角色,
如果任务队列为空, 工作的线程将会被阻塞 (使用条件变量/信号量阻塞)
如果阻塞之后有了新的任务, 由生产者将阻塞解除, 工作线程开始工作
3.管理者线程(不处理任务队列中的任务),1
它的任务是周期性的对任务队列中的任务数量以及处于忙状态的工作线程个数进行检测
当任务过多的时候, 可以适当的创建一些新的工作线程
当任务过少的时候, 可以适当的销毁一些工作的线程

网络

IO多路复用

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

程序员,可以通过accept、 read… .等函数来监测“监听文件描述符”或“通信文件描述符”的读写缓存区,从而获取或发送数据。
I0多路复用,代替了程序员,可以同时监测多个文件描述符并且这个过程是阻塞的,一旦检测到有文件描述符就绪( 可以读数据或者可以写数据)程序的阻塞就会被解除,之后就可以基于这些(一个或多个)就绪的文件描述符进行通信了

​ 平台 检测的连接数 效率

select 跨平台 有上限(1024) 线性表

poll linux 无上限(配置越好,越高) 线性表

epoll linux 无上限(配置越好,越高) 红黑树(高)

1
2
3
4
5
6
水平(LT)模式 和 边沿(ET)模式区别:
如果收数据的buf缓冲区,定为一个小的容量(比如:5字节),且需要收大于5字节的数据时
LT :会不断触发与通知,以每次5字节来读取数据,自动通过N次读取直到读取完毕
ET :一次5字节来读取数据,其余数据,等下次通知再继续读取未读完的数据,所以需要用循环读取数据

综上所述:epoll的边沿模式下 epoll_wait()检测到文件描述符有新事件才会通知,如果不是新的事件就不通知,通知的次数比水平模式少,效率比水平模式要高

linux

两个Linux操作系统之间使用什么命令进行文件的传递?

1
2
3
4
5
6
7
两个Linux操作系统之间可以使用SCP、RSYNC 和 SFTP命令进行文件的传递

SCP(Secure Copy)是一种基于SSH协议的文件传输工具,它可以安全地在两台Linux系统之间复制文件。
RSYNC 是一种更强大的文件复制工具,它不仅可以用于同步文件和文件夹,还可以通过SSH提供安全的数据传输。
SFTP(SSH File Transfer Protocol)也是基于SSH的文件传输协议,提供交互式的文件上传和下载功能。

RSYNC(锐谁克)

常用指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 查看当前进程︰ps
执行退出︰ exit
查看当前路径: pwd
列出指定目录中的目录,以及文件: ls
创建目录 : mkdir
创建文件: vi touch
显示全部: cat
切换目录: cd
复制文件: cp
移动文件: mv
删除文件: rm
查找文件内容:grep
查找文件: find
gcc指令: gcc test.c -o test

驱动加载: insmod
驱动卸载: rmmod

驱动

Uboot启动流程

1
2
3
4
5
第一阶段
初始化时钟,关闭看门狗,关中断,启动 ICACHE ,关闭 DCACHE 和 TLB,关闭 MMU,初始化 SDRAM,初始化 NAND FLASH,重定位。
第二阶段
初始化一个串口,检测系统内存映射,将内核映象和根文件系统映象从 Flash 上读到SDRAM空间中,为内核设置启动参数,调用内核。

Uboot如何引导内核启动的?

1
uboot引导内核启动主要向内核传递三个参数RO,R1,R2,第一个参数RO,默认为0。第二个参数,R1 ,CPU ID,告诉内核板载CPU的型号。第三个参数R2,告诉内核映像文件存在什么地方,板子还剩多少内存空间。这些参数的传递都是以 tag_list 的方式传递的。

为什么需要Uboot?不用行不行?

1
2
用uboot的目的是引导内核启动。
我理解的,理论是可以的。把uboot中所做的一些工作写进内核里,板子也能启动。但是很少有人这么做,毕竟内核很庞大,大面积修改难度比较大。

字符设备有哪些?和块设备有什么区别?如何写一个字符设备驱动?

1
2
3
4
5
6
7
8
9
10
11
字符设备有键盘,鼠标等。

字符设备和块设备的区别主要是访问方式不同,访问字符设备是以字符流的方式访问的,访问块设备是以块为单位,并且可以随机访问。

以一个LED驱动为例,先定义一个 file_operations 结构体,接着编写 init 函数,在init函数中完成对管脚的映射,register_chrdev 字符设备的注册,class_create 类的注册,class_device_create 在类下面注册一个设备。exit 函数中完成字符设备的卸载,类的卸载,内存空间的释放。在open函数中完成硬件管脚的初始化,在write函数中完成点灯操作。

file_operations (fai_欧扑锐醒)
register_chrdev (ruai 急死特_磕带屋)
class_create (亏A特)
class_device_create(低畏死_亏A特)
exit (挨可谁特)

什么是内核空间?什么是用户空间

1
2
3
4
5
6
7
8
32位操作系统而言,它的虚拟地址空间为4G,就是说一个进程的最大地址空间为4G。
操作系统将虚拟地址空间划分为内核空间和用户空间,最高的1G字节为内核空间,低的3G字节为用户空间

或者说

每个进程的4G地址空间中,最高1G都是一样的,即内核空间。只有剩余的3G才归进程自己用。换句话说就是,最高1G的内核空间是被所有进程共享的!

' 其中copy_to_user()用于将内核数据传送给用户空间;copy_from_user()用于将用户空间的数据传送给内核空间。

用户空间和内核空间的通信方式?

1
1.API函数,Copy_from_user , get_user等。2.proc文件系统  3.mmap系统调用  4.使用文件

uboot启动为什么要关闭中断,MMU,DCACHE之类的

1
非必须关闭吧。uboot的目的就是引导内核启动,而且uboot启动的话只是把需要的打开了,其他非必须的都可以关闭。当然也可以打开。DCACHE之类的必须要关i闭,因为SDRAM没有初始化,可能会取到错误的数据。

busybox是什么?

1
缩小版的Unix系统常用命令工具箱。主要包含了一些常用的Linux指令,环境等。

什么是根文件系统

1
根文件系统上是内核启动时所挂载的第一个文件系统,内核代码映像文件保存在根文件系统中。

uboot启动前还需要做那些事情?

1
U-Boot启动前需要完成的主要任务包括确定启动入口、设置环境变量、选择启动介质和参数,以及初始化硬件设备。

bootloader 第一个阶段为什么要足够的小?

1
2
3
第一阶段的Bootloader通常存储在引导扇区中,而引导扇区的大小通常为512字节。因此,第一阶段的Bootloader代码长度必须小于这个限制,以确保能够被完整地加载到内存中。

bootloader (布特漏的)

网络

socket编程的流程

udp : 接收数据 recvfrom(); | 发送数据 sendto(); 不用建立连接 connect() | listen() | accept

服务器端流程

1
2
3
4
5
6
7
8
9
函数 					作用
socket() 创建套接字
bind() 绑定本地IP地址和端口号
listen() 设置监听队列长度
accept() 等待连接
read() 接收信息
close() 关闭套接字

accept(啊克塞pt)

客户端流程:

1
2
3
4
5
6
7
函数					作用
socket() 创建套接字
connect() 发送连接请求
write() 发送信息
close() 关闭套接字

connect(肯来可特)

TCP、UDP 的区别?

1
2
3
4
5
6
1)	TCP是面向连接的,UDP是面向无连接的。
2) TCP是面向字节流的,UDP是基于数据报的。
3) TCP提供可靠服务(正确性、顺序性),UDP提供不可靠服务。
4) TCP程序结构复杂,占用资源多;UDP程序结构简单,占用资源少。
5) TCP有拥塞控制;UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低,适合于实时应用,如IP电话、实时视频会议。
6) TCP只支持一对一;UDP支持一对一、一对多、多对一、多对多。

三次握手的和四次挥手

1
2
3
首先他当然可以设置为更多次数的握手或挥手,每一次的握手、挥手都是传输数据的一个方式,但是他这样会导致更多的资源浪费或更多的开销,这样是并不划算的,所有是要在可行的情况下尽量少的去进行握手和挥手的次数
三次握手就是为保证通信双方的一个接收能力和发送能力,我打个比方,比如:我跟你打电话,我说喂你听得见吗?就相当于一次握手,然后你说我能听见,就确保了我的发送能力和你的接收能力,然后你同时再说一句你有听见吗,我回我能听见,就保证了你的发送能力和我的接受能力,这样的话一共就是3次握手(相当于我发了一个SYN给你然后你回了一个ACK+SYN,这个过程相当于你听到我说你听得到吗,你说我听得到你听得到吗,这两句话其实是在同一时该去说的,所以可以合并为一次握手)
四次挥手,他相当于是我要和你说断开连接,并且你也要和我说断开连接,然后断开连接的请示都是在我们的应用层的代码执行块里面去执行的,然后他的ACK的一个返回,他是在内核态里面去执行的,所以并不能保证我的FIN这个断开连接的请示和ACK是同时发送的,所以他就是一个四次挥手的过程

TCP 为什么是可靠连接?

1
因为TCP传输的数据满足四大条件:不出错、不丢失、不重复、不乱序,而且拥有窗口机制、拥塞控制机制来提高传输效率。

OSI 七层 TCP/IP 四层

1
2
3
4
1)	七层划分为:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。

3) 四层划分为:应用层、传输层、网络层、网络接口层(物理层)

TCP粘包问题

1
2
3
4
5
是指在TCP通信过程中,发送方发送的多个数据包在接收方被粘合成一个数据包的现象。这主要是由于TCP是基于字节流的传输协议,数据没有明确的边界,导致接收方无法准确区分每个数据包的起始和结束位置,从而出现粘包现象‌

解决:
1 ) 通过包头 + 包⻓ + 包体的协议形式,当服务器端获取到指定的包⻓时才说明获取完整。
2 ) 指定包的结束标识,这样当我们获取到指定的标识时,说明包获取完整。

arm

寄存器置 0 或 1 的问题

GPIO 为一个 8 位寄存器 10010100

1
2
3
4
5
6
7
1.现要将它的第 3 位(从右开始,第 0 位起始),置为 1 ,并保持其他位不变。
GPIO |= (1<<3); //置 GPIO_3 为 1
原理是 1 左移3位,再或上 GPIO,并不会改变其他位的值。

2.现要将它的第 2 位,置为 0,并保持其他位不变。
GPIO &= ~(1<<2); //置 GPIO_2 为 0
原理是先将 1 左移 2 位,再进行取反,最后再与上 GPIO,并不会改变其他位的值。

arm有几个寄存器?

1
37个寄存器。

什么是CPSR,SPSR ?什么时候用到?

1
2
CPSR是当前程序状态寄存器,存储的是当前程序的状态
SPSR为备份的程序状态寄存器,主要是中断发生时用来存储CPSR的值的

什么是交叉编译?

1
在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码

SPI 模式

1
SPI 是一种串行外围设备接口,主要用于 CPU 与各种外围设备之间的全双工、同步串行通信。

SPI 有 4 种模式你知道是那 4 种模式吗?

1
2
3
4
5
它通过时钟极性(CPOL)和时钟相位(CPHA)的组合来定义四种工作模
模式0(CPOL = 0, CPHA = 0)‌:空闲时时钟线为低电平,数据在第一个时钟沿(上升沿)采样。
模式1(CPOL = 0, CPHA = 1)‌:空闲时钟线为低电平,数据在第二个时钟沿(下降沿)采样。
模式2(CPOL = 1, CPHA = 0)‌:空闲时钟线为高电平,数据在第一个时钟沿(下降沿)采样。
模式3(CPOL = 1, CPHA = 1)‌:空闲时钟线为高电平,数据在第二个时钟沿(上升沿)采样‌

SPI 有4条线

1
2
3
4
串行时钟线(SCK)
主机输入/从机输出数据线 MISO
主机输出/从机输入数据线 MOSI
低电平有效的从机选择线 SS

I2C

llc协议是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,是一个多主机的半双工通信方式

如何发送一个数据? IIC时序图画下。IC芯片有哪些

1
2
3
4
5
6
7
单片机->从机
刚开始主芯片要发出一个start信号,然后发出一个(用来确定是往哪一个芯片写数据),方向(读/写,0表示写,1表示读)。回应(用来确定这个设备是否存在),然后就可以传输数据,传输数据之后,要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。每传输一个数据,接受方都会有一个回应信号,数据发送完之后,主芯片就会发送一个停止信号。


EEPROM存储器芯片
IIC触摸芯片
IIC隔离芯片

算法和数据结构

实现字符串的库函数

strcpy ()

1
2
3
4
5
void mycpy(char *s1,char *s2)
{
while(*s1++ =*s2++);
}

strlen

1
2
3
4
5
6
int my1en(char *s1)
{
char *p = s1;
while(p++);
return p - s -1;
}

strcat

1
2
3
4
5
6
7
void mycat(char *s1,char *s2)
{
whi1e(*s1++);
s1--;
while(*s1++ = *s2++);
}

算法

反转字符串中的单词

示例1:输入:" Let’s take LeetCode contest "

输出: " s’teL ekat edoCteeL tsetnoc ”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void swap(char *str,int start,int end){
while(start<end)
{
char temp = str[start];
str[start] = str[end];
str[end] = temp;
start++;
end--;
}
}

char reversewords(char * s){
int i = 0;
int start = 0;int end = 0;
int len = str1en(s);
whi1e(s[end++]!='\0 ')
{
//注意边界条件
if(s[end] == ' ' || s[end] == '\0 ')
{
swap(s,start,end-1);
start = end+1;
}
}
return s;
}

一个数组,找出和为S的数字

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

思路: 数组是一个递增排序好的,可以采用双指针法;可以证明的是,a+b=sum, a和b之间相差越大,a*b就越小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vector<int> FindNumbersWithSum(vector<int> array, int sum){
vector<int> ans;
int length = array.size();
if(length < 2)
return ans;
// 定义双指针
int first = 0;
int end = length - 1;
while(first < end){
if(array[first] + array[end] == sum){
ans.push_back(array[first]);
ans.push_back(array[end]);
break;
}
else if(array[first] + array[end] > sum)
end--;
else if(array[first] + array[end] < sum)
first++;
}
return ans;
}

快速排序

1
2
3
基本思想:
快速排序通过选择一个基准元素,将数组分为两部分,一部分小于基准,放左边,一部分大于基准,放右边,然后递归地对这两部分进行快速排序‌

冒泡排序

1
2
基本思想:
把第一个元素与第二个元素比较,如果第一个比第二个大,则交换他们的位置。接着继续比较第二个与第三个元素,如果第二个比第三个大,则交换他们的位置。以此类推

堆排序

1
2
3
4
5
具体步骤
1.将待排序序列构造成一个大顶堆。
2.此时,整个序列的最大值就是堆顶的根节点。3.将其与末尾元素进行交换,此时末尾就为最大值。
4.然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

插入排序

1
2
3
4
1.将待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列;2.取出下一个元素,在已经排序的元素序列中从后向前扫描;
3.如果该元素(已排序)大于新元素,将该元素移到下—位置﹔4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;5.将新元素插入到该位置后;
6.重复步骤2~5

选择排序

1
2
3
4
具体步骤
1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
2.再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3.重复第二步,直到所有元素均排序完毕。

数据结构

数据结构:八种数据结构大全-CSDN博客

二叉树

完满二叉树

1
所有根节点都会有两个子节点。

平衡二叉树

1
平衡二叉树就是左右子树度的高度差值不超过1.

二叉树退化

1
当二叉树的每层节点都被填满时,达到“完美二叉树”;而当所有节点都偏向一侧时,二叉树退化为“链表”。

完美二叉树

1
除了叶子节点之外的每个节点都有两个子节点,并且每一层(包括最后一层)都是完全填满的

完全二叉树

1
完全二叉树从根结点到倒数第二层满足完美二叉树,最后一层可以不完全填充,其叶子结点都靠左对齐

操作系统和计算机组成原理

MMU(存储器管理单元)

1
2
3
4
5
6
7
用来管理虚拟内存系统的器件本身有少量存储空间存放从虚拟地址到物理地址的匹配表。此表称作TLB(转换旁置缓冲区)。所有数据请求都送往MMU,由MMU决定数据是在RAM内还是在大容量存储器设备内。如果数据不在存储空间内,MMU将产生页面错误中断。

MMU的两个主要功能是:
1. 将虚地址转换成物理地址。
2. 控制存储器存取允许。MMU关掉时,虚地址直接输出到物理地址总线。

MMU的作用有两个:地址翻译和地址保护 软件的职责是配置页表,硬件的职责是根据页表完成地址翻译和保护工作。

堆和栈的区别?

1
2
3
4
1.申请方式,栈的空间由操作系统自动分配,释放,堆上的空间手动分配,释放。
2.申请大小,堆的可用空间比较大,栈的可用空间比较小,一般是2M。
3.申请效率,栈申请速度比较慢,堆的申请速度比较快。

堆和栈

1
2
3
堆由开发人员分配和释放, 若开发人员不释放,程序结束时由 OS 回收

栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等

栈的空间为什么不连续

1
栈分配的空间在逻辑地址上是连续的,但在物理地址上是不连续的,因为有 可能相邻的两个字节是在不同的物理分页上

bin文件和elf文件区别

1
2
bin文件是一种纯粹的二进制文件,它只包含机器码
elf文件则是一种复杂的文件格式,用于存储程序或库的代码和数据

什么是哈希表,又如何使用

1
哈希表本质上就是一个数组,只不过数组存放的是单一的数据,而哈希表中存放的是键值对(key - value pair)。 key 通过哈希函数(hash function)得到数组的索引,进而存取索引位置的值。 不同的 key 通过哈希函数可能得到相同的索引值

用户栈和内核栈是同一个区域吗?有什么区别?

1
不是。用户栈和内核栈是两个独立的区域。内核栈保存的是内核态程序运行的时候相关寄存器信息,用户栈保存的是用户态的内容

中断的响应执行流程?听过顶半部和底半部吗?讲讲

1
2
3
cpu接受中断->保存中断上下文跳转到中断处理历程->执行中断上半部->执行中断下半部->恢复中断上下文。

顶半部执行一般都是比较紧急的任务,比如清中断。下半部执行的是一些不太紧急的任务,可以节省中断处理的时间。

uboot启动时使用的是物理地址还是虚拟地址? MMU要开启吗?

1
在uboot阶段并不是必须要开启MMU(内存管理单元),在没开启MMU前使用的是物理地址,开启MMU后使用的是虚拟地址

C

c++ 五个分区(内存分配)

1
2
内存分配情况 堆区、栈区、静态(全局)区、常量区、代码区

指针与引用区别

1
2
3
4
5

指针:指针变量是用来存放内存地址的变量
引用:不是新定义一个变量,而是给已存在变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间.
1.初始化:引用在定义时必须初始化,指针则没有要求(尽量初始化,防止野指针)
引用可以做的事,指针都可以做,引用体现了最小特权原则,指针能够毫无约束的操作内存中的任何东西,尽管功能强大,但是非常危险

重载 重写 隐藏(重定义)

1
2
3
4
5
6
7
8
9
重载: 是相同的函数名,但量它的参数不一样,根据传入不同的参数,去执行不同的函数

重写:指在子类中重新定义从父类继承的函数,以改变其行为。重写的前提是父类的函数必须是虚函数(在 C++ 中使用 virtual 关键字),子类通过相同的函数签名(包括函数名、参数和返回类型)来重新实现该函数
动态多态性:通过指向父类的指针或引用来调用重写的函数时,执行的是子类的实现

隐藏(重定义):
继承关系:同样发生在继承关系中,但与重写不同的是,父类的函数不是虚函数,或者子类定义的函数与父类函数签名不同。
同名但不覆盖:子类的函数与父类的同名函数不会相互覆盖,而是隐藏父类的函数。
静态绑定:根据对象的静态类型调用函数,而不是动态类型。

volatile

1
2
3
防止对寄存器进行优化,使得每条指令都要按照我们写的进行运行

(我 里 太 要)

static

1
2
3
修饰变量的话,这个变量的作用域只是本函数,而且如果多次调用函数的话,这个变量只会被初始化—次。修饰函数的话,函数的作用域只是在本文件内。

(s它踢k)

死循环有几种方式来写

1
2
3
4
for(;1;){}
while ( 1 )
do while(1);

左值和右值

1
左值可写,右值可读。通常,左值可以作为右值,但是右值不一定是左值。

数组名和指针区别

1
2
数组名对应的是一块内存的地址,指针是指向一块内存地址。数组名对应的内存地址不可以修改,指针指向的内存地址可以修改,更加灵活。数组存放的是数据内容,指针存储的是地址。

编程题︰指针函数,函数的参数为in t ,返回值为字符指针

1
2
char *((*p)(int))

宏定义求最大数

1
#define MAx(a, b) (a)>(b)?a:b

typedef 和 define 有什么区别?

1
2
3
4
5
1.typedef在编译时处理,具有类型检查的功能; define在预编译时展开,不会进行错误的检查,只是字符的替换。
2.define没有作用域的限制,typedef有自己的作用域。
3.typedef定义指针的别名时,别名可以连续定义两个指针变量。define定义指针的别名时,使用这个别名连续定义两个指针变量会报错。

typedef (态个打夫)

函数指针和指针函数有什么区别?

1
2
3
4
5
6
如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。
而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。
int(*p)(intint);

首先它是一个函数,只不过这个函数的返回值是一个地址值.
int *pfun(int,int);

野指针是什么?如何避免野指针?

1
2
3
4
5
6
7
野指针是指向不可用内存的指针
当指针被 free 或 delete 释放掉时,如果没有把指针设置为NULL,则会产生野指针
第三个造成野指针的原因是指针操作超越了变量的作用范围。


对指针进行初始化。

int a[5]={1,2,3,4,5},sizeof(a)= ?

1
a是一个包含5个整数的数组。每个整数在32位系统中通常是4字节(即32位)  4*5=20

数组和链表的区别

1
2
数组:数据顺序存储,固定大小;  
链表:链式存储结构, 数据可以随机存储,大小可动态改变

const

1
2
3
4
5

1)可以定义 const 常量
2const 可以修饰函数的参数、返回值,甚至函数的定义体。被 const 修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
const意味着"只读"

什么是智能指针?如何解决其内存泄漏问题?

1
2
3
4
智能指针是一个类,用来存储指针(指向动态分配对象的指针)。使用智能指针能更好的管理堆内存。

引入了弱指针weak_ptr
weak_ptr(为可_戳)

extern"C"” 和 extern 的作用是什么?

1
2
3
4
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按c语言的进行编译,而不是C++的。
extern 用于声明全局变量或函数,以便在其他文件中使用

extern(爱ks等)

C 语言字节对齐的规则,为什么要字节对齐?

1
数据在内存中的地址对齐到特定的边界,以提高内存访问的效率

C++为什么有指针还要引用?

1
为了支持运算符重载。顺便提一句,既然C++引入了引用,那为什么C++不和Java一样抛弃指针呢?答:为了兼容C语言。

C++了解吗?C++11 新特性用的多吗

1
2
3
4
5
6
7
8
9
会的不多,但也有使用,比如:
1. nullptr 出现的目的是为了替代 NULL
2. 区间迭代 - 基于范围的 for 循环
for(auto i : arr) {
std::cout << i << std::endl;
}
3. auto 能用于推导函数的返回类型(自动辨别变量类型)

nullptr(no 婆) auto(窝头)

什么是虚函数

1
2
指向基类的指针在操作它的多态类对象时,可以根据指向的不同类对象调用其相应的函数,这个函数就是虚函数。

c++ 多态

1
2
3
4
实现多态两个必要条件: 1.必须指针或者引用调用虚函数。  2.被调用的函数必须是虚函数。

通俗来说,多态就是多种形态。多态分为编译时多态(静态多态)和运行时多态(动态多态)
比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是优惠买票(5折或75折)。再比如,同样是动物叫的⼀个行为,传猫对象过去,就是"(>ω<)喵",传狗对象过去,就是"汪汪"

virtual

1
2
3
类成员函数前面加virtual修饰,那么这个成员函数被称为虚函数。注意非成员函数不能加virtual修饰。

virtual( w额 挤了)

智能指针

1
2
3
4
5
6
7
作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

auto_ptr、unique_ptr、shared_ptr、weak_ptr(?)
u尼克 谢的
shared_ptr往往用的比较多,他与传统指针的区别是,
对象a析构了之后,对象b又去调用那个指针,它就会发生断错误之类的,就是会报错,但对于智能指针就没这个问题,智能指针在delete之后,它不会马上delete,它内部有一个引用计数器,如果引用计数器不为零,它就永远不会delete

运算符重载

1
2
定义: operator 运算符符号
operator (欧扑锐特)

STL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
'容器
1)顺序容器
vector, deque(带克),list
【vector】
头文件 <vector>
动态数组。元素在内存连续存放。随机存取任何元素都能在常数时间完成。在尾端增删元素具有较佳的性能(大部分情况下是常数时间)。

【deque】
头文件 <deque>
双向队列。元素在内存连续存放。随机存取任何元素都能在常数时间完成(但次于vector)。在两端增删元素具有较佳的性能(大部分情况下是常数时间)。

【list】
头文件 <list>
双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取。


2)关联容器
set, multiset(mou t), map, multimap(mou t)

【set/multiset】
头文件 <set>
set 即集合。set中不允许相同元素,multiset 中允许存在相同的元素。

multiset(mou t)

【map/multimap】
头文件 <map>
map与set的不同在于map中存放的元素有且仅有两个成员变量,一个名为first,另一个名为second, map根据first值对元素进行从小到大排序,并可快速地根据first来检索元素。
map同multimap的不同在于是否允许相同first值的元素。

'迭代器:
iterator 类似于指针,可以用来遍历容器中的元素。

iterator(一特锐特)

'函数:
提供了一些函数对象和仿函数,用于实现自定义的算法行为
greater 仿函数

'适配器:
stack, queue(Q), priority_queue

stack
栈。是项的有限序列,并满足序列中被删除、检索和修改的项只能是最近插入序列的项(栈顶的项)。"后进先出"

出栈 入栈
↖ ↗
\ /
___________
top——> | an |
———————————
| ... |
———————————
| ... |
———————————
| ... |
———————————
bottom——> | a1 |
———————————

stack 是后进先出的数据结构,只能插入,删除,访问栈顶的元素。
可用 vector, list, deque来实现。

queue(Q)】
头文件 <queue>
队列。插入只可以在尾部进行,删除、检索和修改只允许从头部进行。"先进先出"

入队
\

___________
rear——> | an |
———————————
| ... |
———————————
| ... |
———————————
| ... |
———————————
front——> | a1 |
———————————
\

出队

stack 基本类似,可以用 listdeque实现。缺省情况下用deque实现。
同样也有push, pop, top函数。但是push发生在队尾;pop, top发生在队头。先进先出。
有 back 成员函数可以返回队尾元素的引用


priority_queue(拍我瑞踢_Q)】
头文件 <queue>
优先级队列。最高优先级元素总是第一个出列
priority_queue()

'分配器:
负责管理内存的分配和释放,可以自定义来满足特定需求。

类是什么

1
2
3
4
5
6
7
8
9
10
11
类是一种数据类型,用于存储数据和定义行为

类的主要特征
封装,继承,多态

类的结构:
class A{
private(拍我特): 私有成员,只能在成员函数内访问,可以通过 set , get 来更改
public: 公有成员,可以在任何地方访问
protected(扑太克踢的): 保护成员
}

对象是什么

1
是类(class)的实例,具有类的所有属性和方法

类(class)与结构体(struct(s抓克t))和‌ 联合体 的区别是什么

1
2
3
4
5
6
7
8
9
10
11
12
‌C++中类(class)与结构体(struct)的主要区别在于默认访问权限、继承和多态性等方面。
默认访问权限
类:的默认访问修饰符是私有(private
结构体:默认的访问修饰符是公开(public
继承
类‌:类支持继承,并且可以控制继承的访问权限(publicprotectedprivate
结构体‌:结构体也可以继承,但继承默认是公有的,且一般在设计上较少用于复杂继承结构‌
多态性
类‌:类支持多态性
结构体‌:结构体不支持多态性,

联合体:其所有成员共享同一块内存空间。这意味着联合体的每个成员都会覆盖前一个成员的内容,因此联合体只能保存最后一个被赋值的成员的值。

new 和 malloc

1
2
3
4
5
 new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。
new内存分配失败时报错,malloc分配内存失败时返回 NULL
new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。

malloc(马楼克)

new 实现需要类型转换吗?

1
‌在C++中使用new操作符时,通常不需要进行类型转换,因为new操作符本身就负责分配内存并返回正确的类型。

new 底层是用什么实现的?

1
2
3
4
5
  new操作符在底层调用的是 operator new全局函数,而delete操作符在底层调用的是operator delete全局函数。operator new函数实际上是对malloc的封装,当内存申请失败时,它会抛出异常而不是返回 NULL。类似地,operator delete函数是对 free 的封装‌

new(拗)
operator(欧扑锐特)
free(福锐)

构造函数和析构函数能被声明为虚函数吗,为什么?

1
2
3
4
5
6
7
构造函数:
只有基类指针指向子类对象时,虚函数才用意义。当一个基类指针指向子类对象时,子类对象已经构造好了,已经没有动态绑定的必要了,所以构造函数不能是虚函数。


析构函数:
析构函数被调用时,对象的构造已经完成,VPTR(虚指针) 和 VTABLE(虚函数表) 也已被正确初始化,因此虚析构函数在实现上是可能的。
且类有继承时,析构函数常常必须为虚函数。

C++多态,以及怎么实现? 虚函数指针 虚函数表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
提到多态,就不得不提到虚函数,因为虚函数是实现多态的基石
虚函数是用virtual声明类的成员函数,称之为虚函数。
实现多态的三大步:
1.存在继承关系 子类继承父类
2.子类重写父类的virtual function
3.子类以父类的指针或者是引用的身份出现

虚函数的实现原理
其中的关键就是两点:虚函数表指针 、虚函数表

通俗来说,多态就是多种形态。多态分为编译时多态(静态多态)和运行时多态(动态多态)
比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是优惠买票(5折或75折)。再比如,同样是动物叫的⼀个行为,传猫对象过去,就是"(>ω<)喵",传狗对象过去,就是"汪汪"

虚函数表?
1.对于每一个有虚函数的类,编译的时候都会生成一个虚函数表。注意,这个虚函数表是在编译的时候都已经生成。
2.虚函数表里面是一组地址的数组(函数指针数组),他所在的位置就是虚函数表指针里面所存储的地址,它里面所包含的地址就是我们重写了父类的虚函数的地址(没有重写父类的虚函数那么默认的就是父类的函数地址)

内存布局 :
子类不重写父类虚函数的内存布局
此时子类继承了父类的虚函数,但是没有重写,因此子类的虚函数指针指向的表中依然存放的父类的虚函数,因此也可以看到,里面的地址都是一样的
子类重写父类虚函数的内存布局
这个局部变量中可以看到,重写的函数虚函数表中的地址变了,没重写的函数,在虚函数表中的地址依然没变。

虚表指针?
1.每个有虚函数的类的实例里面都有虚表指针。一个有虚函数的类的实例里面有一个或者多个虚表指针,数量取决于有多少个基类是有虚表的。
2.我们把对象首地址开始的4个字节或8个字节,这个位置我们称之为虚函数表指针(可以添加点属性看看位置)。它里面包含一个地址指向的就是虚函数表的地址

虚函数表指针和虚函数表的关系
当类中有虚函数时,就会自动产生一个虚函数表指针,这个指针指向一个虚函数表,表中就存放类中定义的虚函数

红黑树

1
2
3
红黑树是一种自平衡的二叉查找树

红黑树主要用于实现高效的查找和排序,如 Linux 内核中的进程调度和空闲内存的管理,C++ STL库中的 map 和 set 容器等均使用了红黑树实现

hashmap

1
2
3
4
当需要频繁查找数据时,适合用于缓存、索引.

冲突:
当多个键映射到同一个索引时,就会发生冲突。HashMap使用链表或红黑树来处理冲突。初始时,冲突的元素会以链表的形式存储,当链表长度超过阈值时,会转换为红黑树以提高查找效率。

哈希

1
2
3
4
5
6
7
8
9
10
两个或多个不同的键(Key)被哈希函数映射到同一个位置的情况

哈希冲突:
1.开放寻址
‌线性探测‌:当发生冲突时,逐个检查下一个位置,直到找到一个空位置。
二次探测‌:通过平方步长来查找下一个位置,例如14916等。
伪随机探测‌:使用伪随机数生成器来确定下一个位置。

2.链式地址法(拉链法)‌
每个哈希值对应一个链表,冲突的元素插入到对应链表的末尾。

Linux线程并发时,如何改进锁的使用,减少开销?

1
2
3
4
5
6
7
8
9
使用轻量级锁(如果可能,且适用于多处理器系统)。

避免在锁保护下执行长时间操作。

使用读写锁来允许多个线程同时读取数据,而只有一个线程可以修改数据。

使用原子操作来避免使用锁进行简单的计数等操作。

使用无锁算法(例如CAS操作)来避免使用传统的互斥锁。

设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
创建型模式
‌单例模式‌:确保一个类只有一个实例,并提供一个全局访问点‌ | 适用于需要全局唯一对象的情况,如全局状态管理器、全局配置对象等‌
饿汉式‌:在类加载时初始化实例,线程安全,但不支持懒加载。
懒汉式‌:


简单工厂模式:定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常有共同的父类。适用于产品类型相对较少且不太可能经常变动的场景
工厂模式‌:定义一个创建对象的接口,由子类决定实例化哪个类。适用于创建不同类型的对象,如不同类型的组件、数据请求等‌
抽象工厂模式‌:提供一个创建一系列相关或依赖对象的接口。适用于创建一组相互关联的对象,如UI组件‌
‌建造者模式‌:将复杂对象的构建过程与其表示分离,适用于构建复杂对象,如复杂的表单、图表等‌

行为型模式
状态模式‌:允许一个对象在其内部状态改变时改变它的行为‌
中介者模式‌:用一个中介对象来封装一系列对象的交互‌
观察者模式:当一个对象状态发生变化时,所有依赖于它的对象都得到通知并被自动更新‌
策略模式‌:定义一系列算法,并将每一个算法封装起来,使它们可以相互替换‌

拷贝构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Date
{
public:
Date(int year = 2024 ,int month = 3 ,int day = 13) // 构造函数
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d) // 拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void print()
{
cout << "今天的日期是 :" << endl;
cout << _year << '-' << _month << '-' << _day << endl;
}
~Date() // 析构函数
{
_year = 0;
_month = 0;
_day = 0;
}
private:
int _year;
int _month;
int _day;
};

int main()
{
Date d1;
d1.print();

// 创建一个与已存在对象一某一样的新对象
Date d2(d1); // 拷贝构造
d2.print();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
概念:创建一个与已存在对象一某一样的新对象
其唯一的形参必须是引用,但并不限制为const,一般普遍的会加上const限制。

问题1:为什么形参一定要是引用呢?
如果拷贝构造函数中的参数不是一个引用,那么就相当于采用了传值的方式,而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数的参数必须是一个引用 | 为了防止无限制的内存拷贝

问题2:可不可以不用const
const 是为了: 1.防止误操作将原对象内容修改 2.防止传入const对象造成【权限放大】

浅拷贝:
浅拷贝会把指针变量的地址复制
深拷贝:
深拷贝会重新开辟内存空间。 malloc(马楼克):开辟内存空间
当数据成员中有指针时,必须要用深拷贝。

你可以观察在当前这这个类中是否存在显式的析构函数,若是存在的话,表示当前这个类涉及资源管理了【资源管理指得就是去堆中申请空间了】,此时你一定要自己去是实现拷贝构造以达到一个深拷贝;
若是不涉及资源管理的话,直接使用编译器自动生成的进行浅拷贝就可以了
像Date日期类这种只存在【年】、【月】、【日】这种内置类型的浅拷贝就可以了;
像是复杂一些的,例如:链表、二叉树、哈希表这些都会涉及资源的管理,就要考虑到深拷贝了

移动构造函数

1
2
3
4
5
6
移动构造函数使用右值引用作为参数,并且我们将原始对象的资源直接转移到新对象中,而不是进行复制。这就如同快递员将家具从旧的房子搬到新的房子中,节省了时间和空间开销。

总的来说:可以理解为快速的拷贝构造函数

格式:
在构造函数后面写 noexcept(no易克死 塞pt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
C++基础知识
0、基础 基本类型intlong、数组,控制循环for if while swtich, continuebreak 区别
1、c++面向对象,三大特性的理解 继承 封装 多态(一个接口多种实现)
2、c++与c的区别(面向对象与面向过程差异)
3、c++三种继承的特点,public、protect、private 类型
4、c++重载、重写、重定义(动态) 、模板(静态)
https://blog.csdn.net/qq_37018534/article/details/121330896
5、构造函数、析构函数、拷贝构造、赋值运算符等 六种默认函数
6、继承中构造函数与析构函数的调用顺序
7、浅拷贝、深拷贝
8、虚函数与纯虚函数的区别、以及析构函数为啥继承时须要定义为虚析构函数
https://blog.csdn.net/qq_42247231/article/details/105109709
9、c++ stl库有那些vector、list、set、queue、array、set、deque以及各自的优缺点
10、c++11 c++14 c++17有那些新特性,至少要把c++11 14中的新特性了解下
11、c++四种智能指针,为何会出现智能指针 auto_ptr、unique_ptr、shared_ptr、weak_ptr(?)
12、c++四种类型转换 static_castdynamic_castconst_castreinterpret_cast
https://blog.csdn.net/weixin_52983138/article/details/125909103
13、RAII原则用法
12、内存分配情况 堆区、栈区、全局区、文字常量区、代码区
13、指针与引用区别 https://blog.csdn.net/HUAERBUSHI521/article/details/118368696

指针:指针变量是用来存放内存地址的变量
引用:不是新定义一个变量,而是给已存在变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间.
1.初始化:引用在定义时必须初始化,指针则没有要求(尽量初始化,防止野指针)
引用可以做的事,指针都可以做,引用体现了最小特权原则,指针能够毫无约束的操作内存中的任何东西,尽管功能强大,但是非常危险

14、堆栈的区别,先进先出,后进后出等
15conststatic的用法与区别,比如const char* 与char *constconst char* const 区别?func()const作用是什么?
16、右值、右值引用、左值、左值引用
17、c++完美转发
所谓完美转发就是不改变值原本属性进行转发。右值引用时是为了提高效率
18、C++程序中如何调用被C编译器编译后的函数 extern C
19、虚函数表、虚指针关系 (虚析构函数+虚构造函数?)
20、模板与泛型编程template<typename T>与template<clasa A,class B>
21、多种排序算法,如冒泡、二分查找、选择排序、插入排序、快速排序等 时间复杂度
22sizeof与strlen区别,enumstruct区别? classstruct区别?const#define有何优点?typedef 和 define 有什么区别?strcpy、memcpy的区别
23、大小端问题与判定系统大小端,tcp的分包拆包原理
24、可执行程序、动态库、静态库编译的过程 so,a,exe, makefile 了解下或者dll exe,动态库与静态库区别等
25、多线程编程、互斥、同步等,生产者与消费者队列原理加深对锁、信号量、条件变量的理解
26、什么是死锁问题如何解决、什么是内存泄漏如何解决

内存泄露

QT

Qt信号槽?几种连接方式? connect函数

1
2
3
4
5
6
7
8
9
10
11
12
13
以下是 connect 函数的五个参数及其含义:
第一个参数:发送信号的对象
第二个参数:发送信号的信号名
第三个参数:接收信号的对象
第四个参数:接收信号的槽名
第五个参数:连接类型(ConnectionType)

连接类型:
默认连接‌:如果是在同一线程等价于直连,在不同线程等价于队列连接。
‌直连‌:信号在哪,在哪个线程执行。最好只在同一线程中使用‌。
‌队列连接‌:槽在哪就在哪个线程执行。槽函数不会立刻执行,等到接收者的当前执行的函数执行完才会执行槽函数‌1
‌阻塞队列连接‌:在槽函数返回之前,槽函数所在的线程都会被阻塞。
‌唯一连接‌:和直连方式相同,但是只能一对一连接。

为什么用Qt

1
2
3
4
5
跨平台开
高性能和稳定性
丰富的API和工具
灵活的UI设计
多线程编程和国际化支持

Qt控件,自定义控件

1
2
3
4
5
6
7
8
1.创建一个新的类,该类必须继承自 QWidget
2.并在构造函数中设置了控件的样式
3.重写 paintEvent、mousePressEvent 等方法来实现更多的功能
比如:重写 paintEvent 函数,你可以对控件的外观进行自定义
重写 mousePressEvent 函数来处理鼠标点击事件,改变控件的状态或外观
paintEvent(盆特 一问特)
mousePressEvent(猫死 扑ruai死 一问特)
Widget:威几特

PV操作

1
2
一种实现进程互斥与同步的有效方法。‌它通过信号量机制来控制对共享资源的访问,确保在任意时刻只有一个进程可以访问该资源.
在生产者-消费者问题中,PV操作可以用来控制生产者和消费者对共享缓冲区的访问,确保在缓冲区满时生产者等待,缓冲区空时消费者等待。

对QT的理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
QT的核心是槽函数与信号,你不用考虑它像windons那样什么时候调用,你只需要知识那个是接口那个是信号,把它们连接起来,当信号被触发这个动作就会被执行,相比标准的C++需要设置回调函数,qt只需要一个connect,并且有一个父类包含所有的类,就可以在这个类里进行所有的connect

如何调试
断点调试,输出debug日志

比如说,qt程序崩溃了
可能是调用了不该调用的值,就是空指针这种访问了不访访问的内存 经常是这种问题
我那个点餐系统的项目我就遇到过,崩溃的点是在调用空指针那里,你需要定位到那个初始化实例那时,定位的话,是用那个函数的调用栈来回溯看一下是哪里调用过来的(qt下面框那里有的选择),然后往上看,发现是实例没有初始化成功,再看为什么没有初始化成功,输入的参数要包含一些插件,然后检查文件,发现文件下面确实没有放那些插件和库,然后把它装上去之后(就是拷贝到运行目录下之后就正常了)

qt卡死一直转圈圈(界面卡死)
可能是进到了一个死循环,检查一下最近做了什么操作,去对应的响应函数里去找
如果找到,如何解决?
首先把响应的逻辑盘清楚,之后再对那些关键的循环条件进行一个输出,每次输出一下,就可以知道是不是条件有问题,条件有问题,改一下就可以了

假设有一个界面,必须要这个逻辑等数字界面化之后,它就是同步的,该怎么搞?
可以新建一个线程,有后台初始化,然后界面的话是不会卡的,但是还是要等,要等它初始化完

qt版本问题(版本5与版本6的区别)
我用的6.4.05.8.2也用过,
区别很大,qt5为了兼容windows的一些库,但是qt6就已经进行了一个大型的重构,已经没有那种库了,
如果你用qt6是根本不能够运行qt5的项目的,想要编译就必须去qt maintain tools里面把qt5给安装然后在项目里更改使用qt5

多线程下qt的信号与槽分别是在那个线程执行是怎么控制
connect后面有个参数,有个queen(愧),可以去帮助文档里找到那个枚举,你把它改成quee,就是专门用来加入队列的多线程的,这样就不会有那种多线程的问题,如果是单线程,让它默认就好

QSS
是样式设计嘛,我美术功底一般,就了解不多



qt界面开发和json使用方面的经验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
qt
1.1界面开发:
使用Qt Designer:Qt Designer是一个可视化工具,可以快速设计UI。通过拖放控件,生成.ui文件,再通过uic工具将其转换为C++代码。
布局管理:使用布局管理器(如QVBoxLayout、QHBoxLayout、QGridLayout)来管理控件的位置和大小,确保界面在不同分辨率下自适应。
信号与槽机制:Qt的信号与槽机制是处理事件的核心。通过 connect 函数将信号与槽连接,实现控件之间的交互。
1.2 自定义控件
继承QWidget:如果需要自定义控件,可以继承QWidget或其它Qt控件,重写paintEvent等方法来实现自定义绘制。
样式表:使用Qt的样式表(QSS)可以轻松定制控件的外观,类似于CSS。
1.3 多线程
QThread:对于耗时操作,使用QThread来避免界面卡顿。注意线程间的通信,确保线程安全。
信号与槽跨线程:Qt的信号与槽机制支持跨线程通信,但要注意线程间的数据共享问题。

Designer: 滴rai了 Layout: 累奥特 widget: 威几特

JSON使用经验‌:
‌数据交换格式‌:JSON作为一种轻量级的数据交换格式,在Qt开发中得到了广泛应用。它易于人阅读和编写,同时也易于机器解析和生成,非常适合用于网络传输和配置文件。
‌Qt中的JSON支持‌:Qt提供了丰富的API来解析、修改和保存JSON数据。主要使用的类包括QJsonDocument、QJsonArray、QJsonObject和QJsonValue等。这些类提供了便捷的方法来操作JSON数据,如读取、写入、遍历和修改等。
‌数据解析与生成‌:在使用JSON时,经常需要将JSON字符串解析为Qt对象(如QJsonObject、QJsonArray等),或者将Qt对象序列化为JSON字符串。

Document:多克门的 Array:额为

信号槽机制

1
你不用考虑它像windons那样什么时候调用,你只需要知识那个是接口那个是信号,把它们连接起来,当信号被触发这个动作就会被执行,相比标准的C++需要设置回调函数,qt只需要一个connect,并且有一个父类包含所有的类,就可以在这个类里进行所有的connect.以“基于imx6ull的智能家居项目”中的灯光控制为例,在Qt界面中,当用户点击控制LED灯的按钮时,按钮对象会发出一个信号,灯光控制模块连接了按钮的点击信号到一个槽函数上。这个槽函数的职责是根据信号携带的信息(如LED灯应该打开还是关闭)来改变LED灯的实际状态。

事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Qt将系统产生的信号转换成Qt事件,并且将事件封装成类,所以说QT程序是事件驱动的

常见的QT事件类型?
键盘事件: 按键按下和松开
鼠标事件: 鼠标移动,鼠标按键的按下和松开
拖放事件: 用鼠标进行拖放 滚轮事件: 鼠标滚轮滚动
绘屏事件: 重绘屏幕的某些部分 定时事件: 定时器到时
焦点事件: 键盘焦点移动 进入和离开事件: 鼠标移入widget之内,或是移出

QT事件机制有几种级别的事件过滤
从弱到强
重写特定事件处理函数.
重写event()函数
在Qt对象上安装事件过滤器
给QAppliction对象安装事件过滤器.
继承QApplication类,并重写notify()函数.

Qt三大核心机制

1
2
3
4
5
Qt三大核心机制:信号槽、元对象系统、事件模型
元对象系统:三大类(QObject类、Q_OBJECT宏和元对象编译器moc)
QObject是所有Qt对象的基类
Q_OBJECT宏是每个Qt对象类必须使用的宏
元对象编译器moc为 QObject的子类实现元对象特性提供必要的代码。

【C++工程师面试宝典】学习说明_互联网校招面试真题面经汇总_牛客网 (nowcoder.com)

自我介绍

1
自我介绍:面试官你好,我叫***,来自湖北,我的技术栈是C++,熟悉linux的C++/arm开发,熟悉QT有比较丰富的项目经验。所以我想找一份C++开发的工作,非常开心认识您,在这里也提前感谢您接下来的面试,谢谢。

实习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
在实习期间,我参与了实验数据综合处理平台软件的开发工作。该项目是一个军工合作项目,旨在实现实验数据的收集、处理和可视化功能。我的主要贡献包括:
在该项目中,我主要负责了系统数据结构的设计,使用多线程并发实现了多种类型和数据格式的文件导入与解析,处理数据量超过5万条。我还采用了模块化设计来提高软件的可扩展性,并使用全局事件处理机制实现模块间的信号槽连接。此外,你还基于原始数据或处理后的数据实现了数据可视化功能,支持多种绘图方式和文件导出。技术栈方面,我使用了C++、Qt和 Matlab(妈头不)。
通过这些工作,我不仅提升了自己的编程能力,还学会了如何在团队中协作,确保项目按时交付。

Matlab: 主要用于矩阵运算、科学计算和可视化。‌
核心功能与特性‌
‌矩阵计算‌:以矩阵为基本运算单元,支持高效的数值计算与符号运算,简化线性代数、微积分等数学操作‌13
‌可视化与绘图‌:提供丰富的二维/三维图形绘制功能,支持曲面图、动态仿真、数据拟合等复杂场景的可视化‌45
‌工具箱扩展‌:内置‌60+领域专用工具箱‌(如信号处理、深度学习、控制系统等),覆盖工程、金融、生物医学等多学科需求‌16
‌跨语言集成‌:支持与C/C++、Java、Python等语言交互,便于复杂系统的联合开发‌

### 模拟面试官提问及回答

#### 1. **技术细节**
**提问:**
你在项目中使用了哪些技术?能否详细说明你在多线程处理中的具体实现?

**回答:**
在该项目中,我主要使用了C++、Qt和Matlab等技术栈。在多线程处理方面,我利用Qt的QThread类实现了多线程并发机制。具体来说,我针对9种不同类型和3种格式的数据文件设计了独立的线程来负责数据的导入与解析。通过这种方式,我们能够同时处理多个数据文件,显著提高了数据处理的效率。此外,我还采用了信号与槽机制来实现线程之间的通信,确保主线程能够及时获取子线程的处理结果。

#### 2. **模块化设计**
**提问:**
你提到采用了模块化设计,请问这种设计带来了哪些好处?你是如何实现模块间通信的?

**回答:**
模块化设计的主要好处在于提高了软件的可扩展性和维护性。通过将不同的功能模块分离,我们可以独立开发和测试每个模块,从而减少了耦合度,提高了代码的复用性。在实现模块间通信时,我采用了全局事件处理机制。具体来说,每个模块都会注册到一个全局事件总线上,并通过信号槽机制与其他模块进行交互。这种设计不仅简化了模块间的通信,还使得系统的扩展变得更加灵活。

#### 3. **数据可视化**
**提问:**
在数据可视化部分,你们支持了多种绘图方式,请问具体有哪些方式?在实现过程中遇到了哪些挑战?

**回答:**
在数据可视化部分,我们支持了包括折线图、柱状图、散点图等多种绘图方式。用户可以通过绘图菜单对图像的属性进行编辑,并支持5种格式的绘图文件导出。此外,用户还可以将导出的绘图数据重新导入以生成新的图像。

在实现过程中,最大的挑战是如何确保数据可视化功能的高效性和稳定性。由于数据量较大,我们需要优化绘图算法以减少渲染时间。为此,我采用了分层渲染的技术,并对绘图数据进行了预处理,从而显著提升了绘图性能。

#### 4. **项目管理与沟通**
**提问:**
作为实习生,你是如何与项目经理和客户进行沟通的?能否分享一次印象深刻的沟通经历?

**回答:**
在项目管理方面,我主要协助项目经理与客户进行需求沟通和技术交流。我通过定期参加项目会议、编写项目文档等方式,确保项目进展与客户需求保持一致。印象最深刻的一次沟通经历是在项目中期评审时,客户提出了一个新的需求,要求增加对某种新型数据格式的支持。当时距离项目交付仅剩两周时间,我迅速与团队成员讨论解决方案,并向项目经理汇报了可行性分析。最终,我们通过加班加点的努力,在规定时间内完成了新功能的开发和测试,并顺利通过了客户的验收。

#### 5. **问题解决**
**提问:**
在项目实施过程中,你遇到了哪些困难?你是如何解决的?

**回答:**
在项目实施过程中,最大的困难是如何在有限的时间内完成对多种数据格式的支持。由于每种数据格式都有其独特的解析规则,我们需要逐一攻克这些技术难点。为了解决这个问题,我采取了以下措施:

1. **需求分析**:首先对每种数据格式的需求进行详细分析,明确解析规则和数据结构。
2. **模块化开发**:针对每种数据格式设计独立的解析模块,并通过统一的接口进行调用。
3. **自动化测试**:编写自动化测试脚本,对每种数据格式的解析结果进行验证,确保准确性。
4. **团队协作**:与团队成员密切配合,分工合作,共同解决问题。

通过以上措施,我们成功在规定时间内完成了所有数据格式的支持,并确保了系统的稳定性和可靠性。

#### 6. **个人贡献**
**提问:**
在整个项目中,你的个人贡献是什么?你认为自己最大的亮点在哪里?

**回答:**
在整个项目中,我的个人贡献主要体现在以下几个方面:

1. **核心功能开发**:我负责了系统数据结构的设计以及多线程数据导入与解析模块的开发,为项目的顺利推进奠定了基础。
2. **模块化设计与通信机制**:我主导了模块化设计的实现,并通过全局事件处理机制确保了模块间的高效通信。
3. **数据可视化功能实现**:我完成了多种绘图方式的支持,并优化了绘图性能,提升了用户体验。
4. **项目文档撰写与沟通支持**:我协助完成了项目开发文档的撰写,并参与了与项目经理和客户的沟通工作。

我认为自己最大的亮点在于能够将理论知识与实际项目相结合,并在实践中不断学习和创新。无论是面对技术难题还是项目管理挑战,我都能够保持积极的态度并找到有效的解决方案。

---

### 总结
通过以上模拟面试提问与回答,可以看出我在项目中的技术能力、团队协作能力和问题解决能力。我相信这些经验将为未来的职业发展奠定坚实的基础。

‎Z 16:11:49
多线程处理是提升程序性能的重要手段,但如何进一步优化多线程的效率和稳定性是一个复杂且关键的问题。以下从多个角度详细探讨多线程处理的优化方法:

---

### 一、**性能优化**

#### 1. **减少锁竞争**
- **锁粒度细化**:尽量减小锁的范围,避免将过多的操作包含在同一个锁中。例如,可以将一个大锁拆分为多个小锁,分别保护不同的资源。
- **无锁算法**:在某些场景下,可以使用无锁算法(如CAS操作)替代传统的互斥锁,减少锁带来的性能开销。
- **读写锁**:对于频繁读取但较少修改的场景,可以使用读写锁(如`pthread_rwlock_t`),允许多个线程同时读取,但写入时独占锁。

#### 2. **任务划分与负载均衡**
- **细粒度任务划分**:将任务分解为尽可能小的子任务,确保每个线程都能高效执行。
- **动态负载均衡**:根据线程的执行速度和任务的复杂度动态调整任务分配,避免某些线程空闲而其他线程过载。
- **工作窃取(Work Stealing)**:当某个线程完成任务后,可以从其他线程的任务队列中“窃取”任务执行,平衡整体负载。

#### 3. **减少上下文切换**
- **绑定CPU核**:通过绑定线程到特定的CPU核(如使用`pthread_setaffinity_np`),减少线程在不同CPU核之间切换的开销。
- **避免频繁创建和销毁线程**:线程的创建和销毁会有较大的开销,建议使用线程池来复用线程。

#### 4. **利用现代处理器特性**
- **SIMD指令**:在多线程处理中结合SIMD(单指令多数据)指令(如AVX、NEON),加速向量化计算。
- **缓存友好设计**:尽量让数据访问模式符合CPU缓存的工作原理,减少缓存未命中。
- **内存屏障**:合理使用内存屏障(Memory Barrier)确保多线程之间的内存可见性,避免不必要的性能损失。

---

### 二、**资源管理优化**

#### 1. **内存管理**
- **共享内存池**:为多个线程提供共享的内存池,减少内存分配和释放的开销。
- **避免频繁malloc/free**:使用内存池或对象池技术,复用内存块,减少动态内存管理的开销。

#### 2. **I/O操作优化**
- **异步I/O**:对于I/O密集型任务,使用异步I/O(如 epoll、aio)代替同步阻塞操作,避免阻塞线程。
- **零拷贝技术**:在处理大量数据传输时,使用零拷贝技术(如`mmap`或`sendfile`)减少数据复制的次数。

#### 3. **数据库与网络操作**
- **连接池**:对于数据库或网络操作,使用连接池复用连接资源,避免频繁建立和释放连接。
- **批量处理**:将多个操作合并为一个批量请求,减少网络往返次数。

---

### 三、**代码结构优化**

#### 1. **模块化设计**
- 将多线程相关的代码封装为独立的模块,便于维护和扩展。
- 使用接口或抽象类定义线程的行为,提高代码的可测试性和可替换性。

#### 2. **避免竞态条件**
- 竞态条件是多线程程序中最常见的问题之一。可以通过以下方式避免:
- 使用原子操作(Atomic Operations)处理共享变量。
- 将共享资源的访问限制在单一线程中。
- 使用不可变对象减少共享状态的复杂性。

#### 3. **异常处理**
- 在多线程环境中,异常处理尤为重要。确保每个线程都有完善的异常捕获机制,并能够优雅地退出或重试任务。

---

### 四、**调试与测试优化**

#### 1. **多线程调试工具**
- 使用专业的调试工具(如Valgrind、Helgrind、Intel Inspector)检测死锁、竞态条件等问题。
- 使用性能分析工具(如Intel VTune、Google Benchmark)定位多线程程序中的性能瓶颈。

#### 2. **单元测试与集成测试**
- 编写单元测试验证每个线程的功能是否正确。
- 进行集成测试,确保多个线程协同工作时不会出现逻辑错误或性能下降。

#### 3. **压力测试**
- 在高负载场景下测试程序的稳定性,观察是否存在资源泄漏或性能退化问题。

---

### 五、**扩展性优化**

#### 1. **支持动态扩展**
- 设计一个多线程框架时,应支持动态调整线程数量。例如,可以根据系统的负载自动增加或减少线程数。

#### 2. **跨平台兼容性**
- 如果程序需要运行在不同的操作系统上,可以使用跨平台的多线程库(如 pthread、boost.thread、Qt Concurrent)。

#### 3. **分布式扩展**
- 对于非常大的任务规模,可以考虑将多线程处理扩展到分布式系统中(如使用 MPI、Akka、gRPC)。

---

### 六、**实际案例中的优化实践**

#### 1. **数据导入与解析优化**
- 在你的项目中,针对9种类型、3种格式的数据文件导入与解析,可以尝试以下优化:
- 使用内存映射文件(mmap)加速文件读取。
- 将数据解析逻辑拆分为独立的任务,利用线程池进行并行处理。
- 对解析后的数据进行缓存,避免重复计算。

#### 2. **绘图功能优化**
- 在数据可视化部分,可以尝试以下优化:
- 使用GPU加速绘图(如OpenGL、Vulkan)。
- 对绘图数据进行压缩存储,减少内存占用。
- 实现懒加载机制,按需加载绘图数据。

---

### 总结

多线程处理的优化是一个系统性的工程,需要从性能、资源管理、代码结构、调试测试等多个角度综合考虑。通过合理的任务划分、高效的锁机制、充分的资源管理和完善的测试手段,可以显著提升多线程程序的性能和稳定性。在实际开发中,还需要结合具体场景选择合适的优化策略,并通过持续的性能分析和调优来达到最佳效果。


基于imx6ull的智能家居项目

本项目针对智能家居领域,利用IMX6ULL开发板为核心,构建了一套集温湿度监测、远程控制、报警于一体的智能家居解决方案。通过集成DHT11温湿度传感器,SR501红外模块,以及蜂鸣器等硬件组件,实现了环境温湿度的实时监测与展示,并且与应用端通过MQTT协议联动,使用户能够远程查看数据、控制开发板上的LED开关,同时具备高温报警功能,增强家居安全意识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
板子上:服务羰				Linux qt环境下:客户端

传输数据:
用的 TCPsocket, 创建了一个模块结构体,里面都是char类型数据,用于存放获取的模块相应数据。里面有一个 name变量 是存放模块名(也是一个标签名),用于判断,传输过来的数据是那个模块的,比如:dht11模块可以获得温度和湿度的数据,传输过来的数据,我们接受,判断结构体.name 是否等于dht11,等于,我们就取出温度和湿度,数据发送和接受都是用 QByteArray(额瑞) vector(vai k t)容器

客户端:
有一个登陆界面,输出IP和端口,与服务器端连接,服务端(就是板子上),有两个按钮(一个打开服务器按钮,一个关闭服务器按钮),先要打开服务器按钮, 有tcpServer->listen 监听我们给定的IP和端口,客户羰,点击登陆,被客户端监听到,会触发 connected(康来kt) 信号,对应的信号槽函数,就隐藏登陆界面,跳转到主界面,主界面就是一些,获取到模块的数据后进行显示的界面,其中有两个按钮,一个是灯开关按钮,一个是温湿度表按钮,灯开关按钮,点击后,TCPsocket->write,发送开关的数据,比如:1表示开,0表示关。我们也会接收服务器端发过来的数据,TCPsocket->readall,接收后判断name标签名是否等于led,等于,我们就拿数据,同步,开关的状态。如果name=dht11,取的数据就是温湿度的,将数据存到sql文件,用的是mysql, 函数是: db = QSqlDatabase::addDatabase("QMYSQL"), db.setHostName(IP),db.setUserName("root"); db.setPassword("520520"); db.setPort(3306); db.setDatabaseName("ordering_system"); mysql配置好了, 使用的话,QSqlQuery query(db); query.exec("sql语句"),读取sql表数据,select * from 表名,写入sql数据 insert into 表名 values..., 然后是一个温湿度表按钮,点击后,当前界面隐藏,切换到,一个温湿度表界面,读取sql表里的数据展示 setcolumncount(阔伦靠特) 设置列数 ,setRowCount(肉靠特 )行,用query.next()将sql表每一列数据读出,显示到表格中

服务器端:
创建一个用于运行定时器和函数的线程类,写一个构造函数初始化,重写虚函数run,通过run启动线程,timer.setInterval(因特 w额),按指定的周期调用函数,timr.start 启动定时器, exec 进入事件循环,保持线程运行,用它创建了三个定时器线程对象,dth11,sr501,irda,分别每隔512秒执行一次,触发信号并执行三个对应的槽函数
sg90是变量,在 mainWindow 构造函数里,创建tcpServer和tcpSocket对象,用于tpc连接和数据传输,执行三个open1,23函数,用来判断各个模块的设备文件是否打开, open1 里,判断dth11和sg90 ,open2里 ,open2判断sr501和sr04,open3里红外控制和motor(步进电机),为什么分成三份,因为环境监测与调控的功能:sg90是dth11温湿度超过阈值,就自动开启转动,所以它俩我放一个open,智能安防功能:是sr501检测到人了才开启sr04超声波测距的。led灯是与开关按钮关联的

gpio驱动
led
先定义一个 file_operations 结构体,接着编写init函数,在init函数中完成率对管脚的映射,gpio_request 申请引脚,gpio_direction_output 设置方向-输出 ,register_chrdev 字符设备注册, class_create 类的注册,device_create 在类下面创建一个设备 ,exit函数中完成率字符设备卸载,类的卸载,设备号,引脚号的释放,在open函数中完成硬件管脚的初始化,在write函数中完成点灯操作。

sr04 超声波
先定义一个 file_operations 结构体, 接着编写init函数,在init函数中完成率对管脚的映射,我们需要去看文档 sr04原理比较简单我记得,超声波测距模块工作原理:一个控制口发一个10US以上的高电平,就可以在接收口等待高电平输出。一有输出就可以开定时器计时,当此口变为低电平时就可以读定时器的值,此时就为此次测距的时间,方可算出距离。超声波测试距离公式=(高电平时间*声速(340M/S))/2。通过原理,在init的函数中, gpio_request 申请引脚 ,gpio_direction_output 先让引脚输出平常为低电平 ,gpio_to_irq 确定中断号, request_irq 申请中断 ,中断会执行中断函数,中断后,设置定时器 setup_timer,执行定时器函数,中断函数里, gpio_get_value 读取引脚值,判断引脚的电平,上升沿就记录起始时间 ktime_get_ns 获得内核精确的时间 ,错过上升沿就返回, del_timer 中断正常停止定时器,下降记录起始时间,并计算时间差, put_key 时间差存到环形缓冲区, register_chrdev 字符设备注册, class_create 类的注册,device_create 在类下面创建一个设备 ,exit 函数中完成字符设备卸载,类的卸载,设备号,引脚号的释放,在read函数中 get_key 从按键缓冲区中读取按键值(就是时间差),copy_to_user 内核空间的数据复制到用户空间 ,应用层,read函数 读取时间差值,用公式计算得到距离

以一个LED驱动为例,先定义一个 file_operations 结构体,接着编写 init 函数,在init函数中完成对管脚的映射,register_chrdev 字符设备的注册,class_create 类的注册,class_device_create 在类下面注册一个设备。exit 函数中完成字符设备的卸载,类的卸载,内存空间的释放。在open函数中完成硬件管脚的初始化,在write函数中完成点灯操作。
file_operations (fai_欧扑锐醒)
register_chrdev (ruai 急死特_磕带屋)
class_create (亏A特)
class_device_create(低畏死_亏A特)
exit (挨可谁特)

项目采用了C/S架构,利用TCP协议进行通信,通过 MySQL数据库来储存温湿度数据,创建一个用于运行定时器和函数的线程类,用它创建了三个定时器线程对象,dth11,sr501,irda,分别每隔512秒执行一次,触发信号并执行三个对应的槽函数,三个对应的槽函数,对应着温湿度监测功能,红外监测功能等。与这三个定时器线程对象与之对应的有三个open1、23函数,他们是判断各个驱动模块是否加载成功。
还有各模块的驱动编写,主要用 gpio子系统和字符设备 完成驱动编写
还有关于qt移植,需要将qt移植到板子上运行,第一步:准备 qt 库文件和 tslib 库(触摸屏),第二步:交叉编译链,将 qt 和 tslib 设置为自己arm 板子编译工具链 第三步:配置arm板子qt 和 tslib 环境 第四步:qt软件配置,在ubantu 安装qt软件后,进行arm编译环境配置 。然后将qt程序编译后,将该文件复制到板子上,执行即可。
rm板子挂载nfs文件系统就可以和ubantu 共享这个文件夹

遇到的问题:qt移植后,发现在板子上qt文件在运行有问题,是因为ubantu的编译工具链和arm板子编译工具链不一样,我在ubantu qt软件配置的是ubantu的编译工具链,改成 arm板子编译工具链 再 执行编译qt即可

‌模拟技术面试问答‌
'1. 你能简要介绍一下这个智能家居项目的整体架构吗?特别是你提到的C/S架构是如何实现的?
回答示例:
这个项目的整体架构是基于C/S(客户端/服务器)架构的。IMX6ULL开发板作为服务器端,负责采集传感器数据(如温湿度、红外感应等),并通过TCP/Socket或MQTT协议将数据发送到客户端(如手机应用或PC端)。客户端可以远程查看环境数据,并发送控制指令(如开关LED灯)到服务器端。服务器端接收到指令后,执行相应的操作(如控制LED灯的开关),并将执行结果反馈给客户端。

‌二、功能实现细节‌

‌Q2‌:在灯光控制功能中,你是如何实现硬件按钮和Qt界面按钮的同步状态更新的?

‌参考回答‌:

‌硬件按钮状态更新‌:通过GPIO中断检测物理按钮的按下事件,在驱动中更新LED的状态,并将状态信息写入共享内存或通过网络发送至客户端。
‌Qt界面按钮同步‌:Qt客户端定时轮询服务端的状态信息,或者接收服务端主动推送的状态更新信号。当收到状态更新时,界面上的按钮状态会相应地进行更新。

‌Q3‌:在环境监测与调控功能中,你是如何从DHT11模块获取温湿度数据,并在数据到达阈值时开启风扇的?

‌参考回答‌:

‌数据获取‌:通过I2C或GPIO接口与DHT11模块进行通信,读取温湿度数据。
‌阈值判断与风扇控制‌:在服务端程序中设置温湿度阈值,当读取到的数据超过阈值时,通过GPIO控制SG90舵机模拟风扇的开启和关闭。同时,为了避免频繁启停,可以设计迟滞阈值和时间窗口判断逻辑。

‌Q4‌:在智能安防功能中,你是如何协同SR501和SR04模块实现安防检测的?

‌参考回答‌:

‌协同逻辑‌:当SR501人体红外传感器触发后,启动SR04超声波传感器进行距离测量。通过连续测量多次并剔除异常值后取均值,判断是否有入侵者。若距离持续缩小且低于阈值,则判定为入侵,触发声光报警并记录日志。
‌误报处理‌:通过距离变化速率和环境干扰过滤等机制来减少误报。
‌三、驱动开发与系统移植‌

‌Q5‌:在驱动代码编写方面,你主要编写了哪些驱动,并遇到了哪些挑战?

‌参考回答‌:

‌编写的驱动‌:主要包括LED、SR04、DHT11、SR501等硬件模块的驱动。
‌遇到的挑战‌:在驱动开发过程中,主要遇到了硬件兼容性、中断处理、时序控制等方面的挑战。通过查阅硬件手册、调试工具和反复测试,最终成功编写了稳定可靠的驱动代码。

‌Q6‌:在将Qt界面移植到imx6ull板子时,你遇到了哪些挑战,并如何解决的?

‌参考回答‌:

‌遇到的挑战‌:主要包括交叉编译环境配置、显示驱动适配、触摸屏校准等方面。
‌解决方法‌:通过配置Buildroot定制文件系统来解决Qt库依赖冲突问题;启用Framebuffer并关闭桌面环境,直接使用linuxfb插件来适配显示驱动;使用tslib进行触摸屏校准,并通过硬件加速提升渲染效率。
‌四、技术收获与后续应用‌

‌Q7‌:请谈谈你在这个项目中的最大技术收获,并说明如何将这些收获应用到后续的开发中。

‌参考回答‌:

‌技术收获‌:掌握了Linux驱动开发的全流程(编写、加载、调试),深入理解了硬件与内核的交互机制;熟练使用了Qt信号槽机制和ARM平台的移植技巧。
‌后续应用‌:在后续的开发中,可以将这些收获应用到工业物联网项目中,通过复现驱动开发经验来快速实现硬件控制功能;同时,利用Qt快速构建嵌入式GUI界面,提高开发效率。


好的,我们现在开始模拟面试。我会根据你提供的项目经历,特别是基于IMX6ULL的智能家居项目,提出一些问题。请你尽量详细地回答,展示你的技术能力和项目经验。

---

### 1. **你能简要介绍一下这个智能家居项目的整体架构吗?特别是你提到的C/S架构是如何实现的?**

**回答示例:**
这个项目的整体架构是基于C/S(客户端/服务器)架构的。IMX6ULL开发板作为服务器端,负责采集传感器数据(如温湿度、红外感应等),并通过TCP/Socket或MQTT协议将数据发送到客户端(如手机应用或PC端)。客户端可以远程查看环境数据,并发送控制指令(如开关LED灯)到服务器端。服务器端接收到指令后,执行相应的操作(如控制LED灯的开关),并将执行结果反馈给客户端。

---

### 2. **你提到使用了DHT11传感器来采集温湿度数据,能否介绍一下DHT11的工作原理?你是如何将传感器数据读取并传输到客户端的?**

**回答示例:**
DHT11是一种数字温湿度传感器,它通过单总线协议与主控芯片通信。传感器内部集成了湿度和温度检测元件,并将模拟信号转换为数字信号输出。主控芯片通过发送特定的时序信号来触发DHT11的数据采集,DHT11随后会返回40位的数据包,其中包含湿度和温度的测量值。

在项目中,我通过IMX6ULL的GPIO引脚与DHT11进行通信。首先,我编写了驱动代码来初始化GPIO引脚,并通过定时器来精确控制时序信号。当DHT11返回数据后,我解析数据包,提取出温湿度值,并通过TCP/Socket或MQTT协议将这些数据发送到客户端。客户端接收到数据后,可以在界面上实时显示温湿度信息。

---

### 3. **你提到使用了SR501红外模块来实现智能安防功能,能否详细介绍一下这个模块的工作原理以及你在项目中是如何使用它的?**

**回答示例:**
SR501是一种基于红外感应的运动检测模块。它通过检测人体发出的红外辐射来感知是否有人进入监测区域。当检测到人体移动时,SR501会输出高电平信号,否则输出低电平信号。

在项目中,我将SR501的输出引脚连接到IMX6ULL的GPIO输入引脚,并通过编写驱动代码来实时监测SR501的输出状态。当检测到有人进入监测区域时,IMX6ULL会通过TCP/Socket或MQTT协议向客户端发送报警信息,同时触发蜂鸣器发出警报声。这样可以有效增强家居的安全性。

---

### 4. **你在项目中使用了Qt来制作交互界面,能否介绍一下你是如何将Qt与IMX6ULL开发板进行集成的?**

**回答示例:**
在项目中,我使用Qt框架来开发客户端的交互界面。Qt提供了丰富的UI组件和跨平台支持,使得开发过程更加高效。为了将Qt与IMX6ULL开发板集成,我首先在开发板上搭建了Linux操作系统,并安装了Qt的开发环境。然后,我通过交叉编译工具链将Qt应用程序编译为适用于ARM架构的可执行文件,并将其部署到IMX6ULL开发板上。

在开发过程中,我使用了Qt的信号与槽机制来实现界面与后端逻辑的交互。例如,当用户点击界面上的按钮时,界面会发送信号,后端逻辑通过槽函数接收信号并执行相应的操作(如控制LED灯的开关)。同时,Qt的网络模块也帮助我实现了与TCP/Socket或MQTT协议的集成,使得客户端能够实时接收和发送数据。

---

### 5. **你在项目中使用了TCP/Socket通信,能否详细介绍一下你是如何实现客户端与服务器端之间的通信的?**

**回答示例:**
在项目中,我使用了TCP/Socket通信来实现客户端与服务器端之间的数据传输。具体来说,IMX6ULL开发板作为服务器端,监听特定的端口,等待客户端的连接请求。客户端通过Socket连接到服务器端后,双方可以通过TCP协议进行双向通信。

为了实现这一功能,我首先在IMX6ULL上编写了服务器端的Socket程序,使用`bind()`、`listen()`和`accept()`等系统调用来处理客户端的连接请求。一旦连接建立,服务器端就可以通过`send()`和`recv()`函数与客户端进行数据交换。

在客户端,我使用Qt的网络模块来实现Socket通信。通过Qt的`QTcpSocket`类,客户端可以方便地连接到服务器端,并发送和接收数据。例如,当用户点击界面上的按钮时,客户端会通过Socket发送控制指令到服务器端,服务器端接收到指令后执行相应的操作,并将执行结果返回给客户端。

---

### 6. **你在项目中使用了定时器,能否介绍一下定时器在项目中的具体应用场景?**

**回答示例:**
在项目中,定时器主要用于以下几个方面:

1. **传感器数据采集**:为了定期采集DHT11传感器的温湿度数据,我使用了定时器来定时触发数据采集任务。例如,每隔5秒钟,定时器会触发一次数据采集,确保客户端能够实时获取最新的环境数据。

2. **任务调度**:在智能安防模块中,定时器用于定期检查SR501红外模块的输出状态。例如,每隔1秒钟,定时器会触发一次状态检查,确保能够及时检测到有人进入监测区域。

3. **网络通信**:在TCP/Socket通信中,定时器用于检测连接状态。例如,如果客户端长时间没有发送数据,定时器会触发超时处理,关闭连接并释放资源。

通过合理使用定时器,我能够确保系统的各个模块能够高效、稳定地运行。

---

### 7. **你在项目中提到了环境监测与调控功能,能否详细介绍一下这个功能的实现过程?**

**回答示例:**
环境监测与调控功能是通过DHT11传感器采集的温湿度数据来实现的。当温度或湿度超过预设的阈值时,系统会触发相应的调控操作。具体实现过程如下:

1. **数据采集**:通过DHT11传感器定期采集环境温湿度数据,并将数据存储在IMX6ULL的内存中。

2. **阈值判断**:在服务器端,我设置了温度和湿度的阈值(例如温度30°C,湿度70%)。每次采集到新的温湿度数据后,系统会将其与阈值进行比较。如果温度或湿度超过阈值,系统会触发调控操作。

3. **调控操作**:当温度超过阈值时,系统会通过控制SG90舵机来开启风扇,降低环境温度。当湿度超过阈值时,系统会触发报警,提醒用户当前湿度过高。

通过这一功能,用户可以及时了解环境温湿度的变化,并采取相应的措施,确保家居环境的舒适性。

---

### 8. **你在项目中使用了MySQL数据库,能否介绍一下你是如何使用数据库的?**

**回答示例:**
在项目中,我使用MySQL数据库来存储系统的配置数据和历史记录。具体来说,我主要做了以下几方面的工作:

1. **数据库设计**:首先,我设计了数据库的表结构。例如,我创建了一个`environment_data`表来存储温湿度数据,表结构包括`id`、`timestamp`、`temperature`和`humidity`等字段。

2. **数据库连接**:在Qt应用程序中,我使用了Qt的数据库模块(如QSqlDatabase)来连接MySQL数据库。通过配置数据库的连接参数(如主机名、用户名、密码等),我能够成功连接到MySQL数据库。

3. **数据操作**:在Qt应用程序中,我使用了QSqlQuery类来执行SQL语句。例如,当DHT11传感器采集到新的温湿度数据时,我使用`INSERT`语句将数据插入到`environment_data`表中。当客户端需要查询历史数据时,我使用`SELECT`语句从数据库中读取数据。

4. **错误处理**:在数据库操作中,我实现了错误处理机制。例如,当数据库连接失败时,系统会记录错误日志,并尝试重新连接。通过错误处理,我能够确保系统在异常情况下能够正常运行。

通过以上措施,我能够有效地使用MySQL数据库来存储和管理系统的数据。

---

### 9. **你在项目中使用了SR04超声波模块,能否介绍一下这个模块的工作原理以及你在项目中是如何使用它的?**

**回答示例:**
SR04是一种超声波测距模块,它通过发射超声波并接收反射波来测量物体与模块之间的距离。模块的工作原理是通过计算超声波从发射到接收的时间差,结合声速来计算距离。

在项目中,我将SR04模块用于智能安防功能。具体来说,SR04模块用于检测是否有人靠近。当检测到有人靠近时,SR04会输出距离数据,我通过IMX6ULL的GPIO引脚读取这些数据,并通过TCP/Socket或MQTT协议将数据发送到客户端。客户端接收到数据后,可以在界面上显示距离信息,并触发报警功能。

---

### 10. **你在项目中使用了Qt的信号与槽机制,能否详细介绍一下你是如何使用这一机制来实现模块间的通信的?**

**回答示例:**
Qt的信号与槽机制是一种强大的事件处理机制,允许对象之间进行松耦合的通信。在项目中,我使用信号与槽机制来实现界面与后端逻辑之间的交互。

例如,在智能家居项目中,当用户点击界面上的按钮时,按钮会发出一个`clicked()`信号。我通过`connect()`函数将这个信号连接到一个槽函数,槽函数中包含了控制LED灯开关的逻辑。当按钮被点击时,槽函数会被自动调用,执行相应的操作。

此外,我还使用信号与槽机制来处理网络通信。例如,当客户端接收到服务器端发送的数据时,`QTcpSocket`会发出一个`readyRead()`信号。我通过将这个信号连接到一个槽函数,实现了数据的实时处理和显示。

通过合理使用信号与槽机制,我能够确保系统的各个模块之间能够高效、灵活地进行通信。

---

### 11. **你在项目中使用了IMX6ULL开发板,能否介绍一下你是如何进行硬件驱动测试的?**

**回答示例:**
在项目中,我使用了多种方法来进行硬件驱动测试,主要包括:

1. **单元测试**:在编写驱动代码时,我首先对每个模块进行单元测试。例如,对于LED驱动,我编写了测试代码来验证GPIO引脚的初始化和控制功能。通过单元测试,我能够确保每个模块的功能正常。

2. **集成测试**:在完成各个模块的驱动代码后,我进行了集成测试。例如,我将DHT11传感器、SR501红外模块和蜂鸣器连接到IMX6ULL开发板上,并编写测试代码来验证它们之间的协同工作。通过集成测试,我能够确保系统的各个模块能够正常交互。

3. **硬件调试工具**:在测试过程中,我使用了万用表和示波器等硬件调试工具来监测信号的电平和波形。例如,我使用示波器来监测DHT11传感器的时序信号,确保其符合数据手册中的要求。

通过以上测试方法,我能够确保硬件驱动的正确性和稳定性。

---

### 12. **你在项目中使用了Linux系统编程,能否介绍一下你在项目中编写了哪些驱动代码?**

**回答示例:**
在项目中,我编写了多个硬件模块的驱动代码,主要包括:

1. **LED驱动**:通过GPIO子系统控制开发板上的LED灯。我编写了驱动代码来初始化GPIO引脚,并通过读写寄存器来控制LED的开关。

2. **DHT11驱动**:通过单总线协议与DHT11传感器通信。我编写了驱动代码来初始化GPIO引脚,并通过定时器来精确控制时序信号,确保能够正确读取温湿度数据。

3. **SR501驱动**:通过GPIO输入引脚读取SR501红外模块的输出状态。我编写了驱动代码来实时监测SR501的输出信号,并在检测到人体移动时触发报警。

4. **SR04驱动**:通过GPIO引脚控制SR04超声波模块的测距功能。我编写了驱动代码来初始化GPIO引脚,并通过定时器来精确控制超声波的发射和接收。

通过编写这些驱动代码,我能够有效地控制硬件模块,并实现智能家居系统的各项功能。

---

### 13. **你在项目中使用了Linux命令,能否介绍一下你在项目中常用的Linux命令有哪些?**

**回答示例:**
在项目中,我经常使用以下Linux命令:

1. **文件操作**:`ls`(列出目录内容)、`cd`(切换目录)、`cp`(复制文件)、`mv`(移动文件)、`rm`(删除文件)等。

2. **进程管理**:`ps`(查看进程状态)、`kill`(终止进程)、`top`(实时查看系统资源使用情况)等。

3. **网络配置**:`ifconfig`(配置网络接口)、`ping`(测试网络连接)、`netstat`(查看网络状态)等。

4. **系统调试**:`dmesg`(查看内核日志)、`strace`(跟踪系统调用)、`gdb`(调试程序)等。

5. **软件安装**:`apt-get`(安装软件包)、`dpkg`(管理Debian软件包)等。

通过熟练使用这些Linux命令,我能够高效地进行系统配置、调试和管理。

---

### 14. **你在项目中使用了TCP/多线程等技术,能否介绍一下你是如何确保系统的稳定性和性能的?**

**回答示例:**
在项目中,我通过以下几种方式来确保系统的稳定性和性能:

1. **多线程编程**:为了提高系统的并发处理能力,我使用了多线程技术。例如,在数据采集模块中,我创建了单独的线程来处理DHT11传感器的数据采集,避免阻塞主线程的执行。通过合理使用多线程,我能够确保系统的各个模块能够并行运行,提高系统的响应速度。

2. **线程同步**:在多线程编程中,我使用了互斥锁(mutex)和条件变量(condition variable)来实现线程间的同步。例如,在数据共享时,我使用互斥锁来保护共享资源,避免数据竞争。

3. **错误处理**:在TCP/Socket通信中,我实现了完善的错误处理机制。例如,当网络连接中断时,系统会自动重连,并记录错误日志。通过错误处理,我能够确保系统在异常情况下能够正常运行。

4. **性能优化**:在系统设计时,我考虑了性能优化。例如,在数据采集模块中,我使用了定时器来定期采集数据,避免频繁的传感器访问。通过性能优化,我能够确保系统在高负载情况下仍能保持稳定运行。

通过以上措施,我能够确保系统的稳定性和性能。

---

### 15. **你在项目中使用了Qt的UI设计,能否介绍一下你是如何设计用户界面的?**

**回答示例:**
在项目中,我使用Qt Designer来设计用户界面。Qt Designer是一个可视化的UI设计工具,允许我通过拖拽组件的方式来快速构建界面。在设计过程中,我主要考虑了以下几个方面:

1. **界面布局**:我使用了Qt的布局管理器(如QVBoxLayout、QHBoxLayout等)来管理界面组件的布局。通过合理使用布局管理器,我能够确保界面在不同分辨率下都能正常显示。

2. **组件选择**:根据项目需求,我选择了合适的Qt组件来构建界面。例如,我使用了QPushButton来实现按钮功能,使用了QLabel来显示文本信息,使用了QLineEdit来接收用户输入等。

3. **信号与槽机制**:在界面设计中,我使用了Qt的信号与槽机制来实现界面与后端逻辑的交互。例如,当用户点击按钮时,按钮会发出`clicked()`信号,我通过`connect()`函数将这个信号连接到一个槽函数,槽函数中包含了控制LED灯开关的逻辑。

4. **界面美化**:为了提高用户体验,我对界面进行了美化。例如,我使用了Qt的样式表(QSS)来设置组件的颜色、字体和边框等属性,使界面更加美观。

通过以上设计,我能够构建出一个功能完善、易于使用的用户界面。

---

### 16. **你在项目中使用了MySQL数据库,能否介绍一下你是如何使用数据库的?**

**回答示例:**
在项目中,我使用MySQL数据库来存储系统的配置数据和历史记录。具体来说,我主要做了以下几方面的工作:

1. **数据库设计**:首先,我设计了数据库的表结构。例如,我创建了一个`environment_data`表来存储温湿度数据,表结构包括`id`、`timestamp`、`temperature`和`humidity`等字段。

2. **数据库连接**:在Qt应用程序中,我使用了Qt的数据库模块(如QSqlDatabase)来连接MySQL数据库。通过配置数据库的连接参数(如主机名、用户名、密码等),我能够成功连接到MySQL数据库。

3. **数据操作**:在Qt应用程序中,我使用了QSqlQuery类来执行SQL语句。例如,当DHT11传感器采集到新的温湿度数据时,我使用`INSERT`语句将数据插入到`environment_data`表中。当客户端需要查询历史数据时,我使用`SELECT`语句从数据库中读取数据。

4. **错误处理**:在数据库操作中,我实现了错误处理机制。例如,当数据库连接失败时,系统会记录错误日志,并尝试重新连接。通过错误处理,我能够确保系统在异常情况下能够正常运行。

通过以上措施,我能够有效地使用MySQL数据库来存储和管理系统的数据。

---

### 17. **你在项目中使用了Linux系统编程,能否介绍一下你是如何进行进程间通信的?**

**回答示例:**
在项目中,我使用了多种进程间通信(IPC)机制来实现不同进程之间的数据交换,主要包括:

1. **管道(Pipe)**:管道是一种半双工的通信机制,适用于父子进程之间的通信。在项目中,我使用管道来实现进程间的简单数据传递。

2. **消息队列(Message Queue)**:消息队列是一种全双工的通信机制,适用于多个进程之间的通信。在项目中,我使用消息队列来实现进程间的异步通信。

3. **共享内存(Shared Memory)**:共享内存是一种高效的通信机制,适用于需要频繁交换大量数据的进程。在项目中,我使用共享内存来实现进程间的快速数据交换。

4. **信号(Signal)**:信号是一种异步通信机制,适用于进程间的简单通知。在项目中,我使用信号来实现进程间的简单通信,例如通知某个进程执行特定的操作。

通过以上IPC机制,我能够确保系统的各个进程之间能够高效、稳定地进行通信。

---

### 18. **你在项目中使用了Qt移植到ARM的过程,能否详细介绍一下你是如何完成这一过程的?**

**回答示例:**
将Qt移植到ARM架构的IMX6ULL开发板的过程主要包括以下几个步骤:

1. **搭建交叉编译环境**:首先,我在PC上安装了交叉编译工具链(如arm-linux-gnueabihf),用于将Qt应用程序编译为适用于ARM架构的可执行文件。

2. **配置Qt源码**:接下来,我下载了Qt的源码,并使用`configure`脚本进行配置。在配置过程中,我指定了交叉编译工具链和目标平台(ARM),并启用了必要的模块(如Qt Core、Qt GUI、Qt Network等)。

3. **编译Qt库**:配置完成后,我使用`make`命令编译Qt库。编译过程可能需要较长时间,具体取决于PC的性能和Qt的版本。

4. **部署到开发板**:编译完成后,我将生成的Qt库和应用程序部署到IMX6ULL开发板上。为了确保应用程序能够正常运行,

基于Linux、QT、C++的点餐系统

基于Linux、QT、C++的点餐系统_基于qt的点餐系统-CSDN博客 对图内容更好理解

碰到项目中的问题就说内存泄露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
1、项目采用 C/S 架构,利用 TCP 协议进行通信,采用了多线程的方法解决服务端与多个客服端的通信。
2、通过 vector 容器来存放顾客的订单信息,顾客提交订单时,将容器发送给管理端和厨房。
3、通过 MySQL数据库来储存用户信息、菜单信息、账单信息,及厨房待做餐品清单。
4、采用中介者模式和状态模式,实现不同客服端间的信息交互。
5、顾客提交订单后可随时进行加餐,对未上餐品可进行催餐和退餐。
6、收银员可查询相应桌号的消费详情和消费总额并进行结账。
7、管理员可进行对用户信息和菜单信息的修改、添加和删除,并可查看账单及账单详情。

顾客端(客户端):提供用户点餐界面,餐品信息,未上餐品信息,已上餐品信息
厨房端(客户端):在顾客端用户提交的点餐的订单,就是待做的餐品信息
收银端(客户端):登录后,餐桌使用情况(某号餐桌是否有人使用中),使用的某餐桌的用餐的费用情况,可选择是否要结账
管理端(服务端):登录后,管理用户账号,查看账单情况,菜单管理情况,饮品管理情况

mysql
MySQL数据库用于储存用户信息、菜单信息、账单信息等。
QSqlDatabase::addDatabase("QMYSQL"); 添加数据库 ,setHostName ip名,setUserName用户名,setPassword 密码,setPort 端口 setDatabaseName 数据库名,就连接到数据库了 使用的话,还需要 QSqlQuery query; 比如:query.exec("sql语句"),要选择数据的话,sql语句 select ... from 表名,如果是要写入数据的话,sql语句 insert into ... 表名。 query.next() 读取下一行信息,query.value(“字段名”) 就相当于键值对的获取方法

多线程
根据新的客户端连接的个数,来创建
new QTcpServer 新建监听的套接字 , listen 监听 ,connect 信号与槽函数,信号是 newConnection 有新的连接,我们就新建新的套接字:客服端套接字,nextPendingConnection 拿到新的套接字,将新套接字当作参数创建线程 new mythread, thread->start() 开启进程, 进程文件 mythread , run函数,信号与槽函数, read信号,读数据,QByteArray 接收 readall() 所以的数组,再将 QByteArray 接收 的变量,作为参数传给一个函数,该函数是客户端信息标志位判断,该函数中,先用,定义的容器结构体去接收数据,strcmp 字符串比较函数,拿容器数据里的标志位对比,等于0,我们就执行对应的函数,如果标志位是厨房端或收银端,还需要将新的套接字tcpsocket给厨房端或收银端。


数据
结构体,一个餐桌结构体 FoodInfo , 一个菜单结构体MenuInfo , vector 容器来存储数据,MenuVec 菜单容器和 2个Foodvec订单容器,其中一个是数组类型 ,还有定义的两个对应的 菜单结构体 和 订单结构体,是一个头部,用tcp read和write读写时会用到, 存储数据的时候方法是一样的,比如是菜单相关的数据,用 QByteArray 接收数据,可以先将数据转成字符流 toLocal8Bit 再接收,再用 memcpy 将 QByteArray 变量.tata(),它的数据复制给 定义的 char 字符变量,再用strcpy,将 存放在字符变量中的数据,再次复制给 菜单结构,再用 push_back 将菜单结构添加到 MenuVec 菜单容器,这样,我们在用TCP写数据传递时,直接把 相应的容器 传过去


界面数据显示
tablewidget 操作
QStringList 表标题, setColumnCount 列表设置为和表标题列相等 , setRowCount 列表行为0 由多少条数据决定 HorizontalHeaderLabels 插入表头 ,while( query.next()) 读取下一行信息, new QTableWidgetItem("") 参数为id,获取就都是id列数列,就可以指定某列的数据,限制内容,再用 ui->tableWidget->setItem ,指定某行某行数据,就可以完成数据内容以表的形式显示


### 面试官提问与求职者解答

' 1. **请简要介绍一下你在“基于Linux、QT、C++的点餐系统”项目中的角色和职责。**
- **求职者回答**:
在这个项目中,我主要负责了系统的整体架构设计和部分功能模块的开发。具体来说,我负责了管理端、订单端、收银端和厨房端的功能实现。管理端包括用户管理、菜单管理、账单管理等功能;订单端负责点餐、加餐、退餐等操作;收银端负责餐桌状态管理和结账功能;厨房端则负责待做餐品的管理和上菜操作。此外,我还参与了数据库的设计和TCP通信模块的开发。

' 2. **你提到项目采用了C/S架构,能否详细说明一下客户端和服务端之间的通信机制?**
- **求职者回答**:
项目采用了C/S架构,客户端和服务端之间通过TCP协议进行通信。服务端使用多线程来处理多个客户端的请求,确保并发访问时的稳定性和响应速度。客户端通过TCP套接字与服务端建立连接,发送请求并接收服务端的响应。我们使用了Qt的信号与槽机制来处理异步通信,确保界面操作的流畅性。

' 3. **你在项目中使用了多线程,能否详细说明一下多线程的应用场景以及你是如何解决多线程并发问题的?**
- **求职者回答**:
在项目中,多线程主要用于处理服务端与多个客户端的并发通信。每个客户端连接都会创建一个独立的线程来处理其请求,这样可以避免阻塞主线程,确保系统的响应速度。为了处理多线程并发问题,我们使用了Qt的`QMutex`和`QWaitCondition`来进行线程同步,确保共享资源(如订单信息、菜单信息等)的线程安全。

' 4. **你提到使用了中介者模式和状态模式,能否详细说明一下这两种设计模式在项目中的应用?**
- **求职者回答**:
中介者模式主要用于不同客户端之间的信息交互。我们设计了一个中介者类,负责协调管理端、订单端、收银端和厨房端之间的通信。通过中介者模式,各个客户端不需要直接相互通信,而是通过中介者来传递消息,降低了模块之间的耦合度。
状态模式则用于处理订单的状态转换。例如,订单从“待处理”到“已处理”再到“已完成”的状态转换。我们为每个状态设计了一个状态类,订单对象根据当前状态调用不同的行为,从而简化了状态管理的复杂性。

#### 5. **你在项目中使用了MySQL数据库,能否详细说明一下数据库的设计以及你是如何优化数据库操作的?**
- **求职者回答**:
数据库设计方面,我们主要设计了用户表、菜单表、订单表和账单表。用户表存储用户信息,菜单表存储菜品信息,订单表存储顾客的点餐信息,账单表存储每笔交易的详细信息。
为了优化数据库操作,我们使用了索引来加速查询操作,特别是在账单查询和菜单查询时。此外,我们还使用了事务处理来确保数据的完整性,特别是在订单提交和账单结算时,确保多个操作要么全部成功,要么全部失败。

#### 6. **你在项目中使用了STL容器,能否详细说明一下你是如何使用`vector`容器来管理订单信息的?**
- **求职者回答**:
在项目中,我们使用`vector`容器来存储顾客的订单信息。每当顾客提交订单时,订单信息会被添加到`vector`容器中。然后,我们将这个容器发送给管理端和厨房端,以便他们能够实时更新订单状态。`vector`容器的动态数组特性使得我们可以方便地进行订单的添加、删除和修改操作。此外,我们还使用了迭代器来遍历`vector`容器,确保订单信息的准确传递。

#### 7. **你在项目中如何处理顾客的加餐、退餐和催餐操作?**
- **求职者回答**:
对于加餐操作,顾客可以在订单提交后继续添加菜品,系统会实时更新订单信息并通知厨房端。对于退餐操作,顾客可以选择未上菜的菜品进行退餐,系统会更新订单状态并通知厨房端取消相应的菜品。催餐操作则是通过向厨房端发送催餐请求,厨房端会根据当前订单状态优先处理该订单。这些操作都通过TCP通信实时同步到各个客户端,确保数据的一致性。

#### 8. **你在项目中如何处理收银端的结账功能?**
- **求职者回答**:
收银端的结账功能主要包括查询餐桌的消费详情和消费总额。当顾客准备结账时,收银员可以选择相应的餐桌号,系统会从数据库中查询该餐桌的所有订单信息,并计算出总消费金额。收银员确认后,系统会更新账单表,并将餐桌状态设置为“空闲”,以便下一批顾客使用。结账过程中,我们使用了事务处理来确保账单数据的准确性和一致性。

#### 9. **你在项目中如何处理管理端的用户管理和菜单管理功能?**
- **求职者回答**:
管理端的用户管理功能包括用户的添加、修改、删除和查询。管理员可以通过界面操作对用户信息进行管理,所有的操作都会实时更新到数据库中。菜单管理功能类似,管理员可以添加、修改、删除菜品信息,并实时更新到菜单表中。我们还为管理员提供了账单查询功能,管理员可以查看所有账单的详细信息,包括每笔交易的消费明细。

#### 10. **你在项目中遇到的最大挑战是什么?你是如何解决的?**
- **求职者回答**:
项目中最大的挑战是处理多个客户端之间的实时通信和数据同步。由于系统涉及多个客户端(管理端、订单端、收银端、厨房端),如何确保各个客户端之间的数据一致性是一个难题。我们通过使用中介者模式和TCP通信机制来解决这个问题。中介者模式负责协调各个客户端之间的通信,而TCP通信确保了数据的实时同步。此外,我们还使用了数据库事务处理来确保数据的完整性,最终成功解决了这个问题。

#### 11. **你在项目中有没有进行性能优化?如果有,你是如何进行的?**
- **求职者回答**:
是的,我们在项目中进行了一些性能优化。首先,我们对数据库查询进行了优化,使用了索引来加速查询操作,特别是在账单查询和菜单查询时。其次,我们对TCP通信进行了优化,使用了多线程来处理多个客户端的并发请求,确保系统的响应速度。此外,我们还对界面进行了优化,使用了Qt的信号与槽机制来处理异步操作,确保界面操作的流畅性。

#### 12. **你在项目中使用了哪些Qt的特性?能否详细说明一下?**
- **求职者回答**:
在项目中,我们主要使用了Qt的信号与槽机制、多线程支持和界面设计工具。信号与槽机制用于处理界面事件和异步通信,确保界面操作的流畅性。多线程支持用于处理服务端与多个客户端的并发通信,确保系统的响应速度。界面设计工具则用于设计各个客户端的用户界面,确保界面的美观和易用性。此外,我们还使用了Qt的数据库模块来连接和操作MySQL数据库。

#### 13. **你在项目中有没有进行过错误处理?如果有,你是如何处理的?**
- **求职者回答**:
是的,我们在项目中进行了错误处理。首先,我们对数据库操作进行了错误处理,使用了事务处理来确保数据的完整性。如果某个操作失败,系统会回滚事务,确保数据的一致性。其次,我们对TCP通信进行了错误处理,使用了Qt的错误处理机制来捕获和处理通信过程中的异常情况。此外,我们还对用户输入进行了验证,确保用户输入的数据符合预期格式,避免因错误输入导致的系统异常。

#### 14. **你在项目中有没有进行过测试?如果有,你是如何进行测试的?**
- **求职者回答**:
是的,我们在项目中进行了一系列的测试。首先,我们进行了单元测试,对每个功能模块进行了单独的测试,确保每个模块的功能正常。其次,我们进行了集成测试,测试各个模块之间的交互是否正常。最后,我们进行了系统测试,模拟实际使用场景,测试整个系统的稳定性和性能。我们还使用了Qt的测试框架来进行自动化测试,确保测试的全面性和准确性。

#### 15. **你在项目中学到了哪些新的技术或技能?**
- **求职者回答**:
在这个项目中,我学到了很多新的技术和技能。首先,我深入理解了C/S架构的设计和实现,特别是TCP通信和多线程编程。其次,我学会了如何使用Qt进行界面设计和事件处理。此外,我还学会了如何使用MySQL数据库进行数据存储和管理,以及如何进行数据库优化和事务处理。最后,我还学会了如何使用设计模式(如中介者模式和状态模式)来解决复杂的系统设计问题。

#### 16. **如果你有机会重新做这个项目,你会做出哪些改进?**
- **求职者回答**:
如果我有机会重新做这个项目,我会考虑以下几点改进:
1. **引入更高级的负载均衡机制**:目前的系统在处理大量并发请求时可能会遇到性能瓶颈,未来可以考虑引入负载均衡机制来分担服务端的压力。
2. **增加更多的错误处理和日志记录**:虽然我们已经进行了一些错误处理,但未来可以增加更多的错误处理机制和日志记录,以便更好地排查和解决问题。
3. **优化数据库设计**:虽然我们已经对数据库进行了优化,但未来可以考虑引入更多的数据库优化技术,如分表、分区等,以进一步提高数据库的性能。
4. **引入更多的自动化测试**:虽然我们已经进行了一些自动化测试,但未来可以引入更多的自动化测试工具和框架,确保系统的稳定性和可靠性。

---

### 总结:
通过这些问题和回答,面试官可以全面了解求职者在“基于Linux、QT、C++的点餐系统”项目中的技术能力、问题解决能力以及项目经验。求职者在回答中展示了扎实的编程基础、良好的系统设计能力以及对多线程、数据库、设计模式等技术的深入理解。

动量轮平衡自行车

1
2
3
4
5
6
7
采用stm32f103c8t6作为主控芯片,通过MPU6050获取姿态参数,通过PID参数调节,直立环输出pwm控制电机驱动驱动电机,电机带动动量轮,实现自主平衡,有OLED显示屏作为显示模块,N20直流减速电机 ,TB6612FNG电机驱动模块、舵机、HC-06蓝牙模块、HC-RS04超声波模块、

PID:
假设我们要对无人机进行PID参数调试
KP的越大,螺旋桨转的越快,向上的力就越大,误差也越大,响应速度也越快,产生的震荡幅度也越大.
KD, 会抵消部分KP的升力(也就是向上的力),让其震荡幅度变小
KI, 无人机的误差存在,无人机的升力也一定存在,当无人机接近目标的高度时,最终会和重力平衡,此时无人机将不会再上升,为了解决这个问题,我们需要累积这个误差值,如果长时间无人机得不到修正,我们需要给无人机提供更大的升力,让无人机上升与目标高度一致

整体面试流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
' 面试问题 1: 
**你在广州飞进信息科技有限公司的实习经历中,提到了使用多线程并发处理数据。你能详细解释一下你是如何实现多线程并发的吗?特别是在处理3种类型、3种格式数据文件时,如何确保线程安全和数据一致性?**

**回答:**
在项目中,我使用了Qt的QThread类来实现多线程并发。具体来说,我为每种类型的数据文件创建了一个独立的线程来处理数据的导入和解析。为了确保线程安全,我使用了Qt的信号与槽机制来进行线程间的通信,避免了直接共享数据。此外,我还使用了QMutex来保护共享资源,确保在同一时间只有一个线程可以访问和修改这些资源。

对于数据一致性,我在每个线程中使用了局部变量来处理数据,只有在数据完全处理完毕后才通过信号与槽机制将结果传递到主线程。这样可以避免在数据处理过程中出现数据不一致的情况。同时,我还使用了QWaitCondition来协调线程的执行顺序,确保数据处理的顺序性和一致性。

' 面试问题 2:
**你在基于Linux的Qt点餐系统项目中提到了使用TCP/多线程/Socket通信。你能详细说明一下你是如何设计这个系统的通信机制的吗?特别是在多客户端的情况下,如何确保通信的稳定性和效率?**

**回答:**
在这个项目中,我采用了C/S架构,服务器端使用Qt的QTcpServer类来监听客户端的连接请求,并为每个连接的客户端创建一个独立的QTcpSocket对象来处理通信。为了支持多客户端并发,我使用了多线程技术,每个客户端的通信都在一个独立的线程中进行,避免了阻塞主线程。

为了确保通信的稳定性,我实现了心跳机制,定期检查客户端与服务器的连接状态。如果发现连接断开,服务器会尝试重新连接或通知系统管理员。此外,我还使用了Qt的信号与槽机制来处理异步通信,确保数据的及时传输和处理。

为了提高通信效率,我使用了数据包的分片和重组技术,将大数据包分成多个小数据包进行传输,减少了网络延迟的影响。同时,我还使用了Qt的QDataStream类来进行数据的序列化和反序列化,确保数据的高效传输和解析。

' 面试问题 3:
**你在基于imx6ul的智能家居项目中提到了驱动代码的编写。你能详细说明一下你是如何编写和调试这些驱动的吗?特别是在Qt移植到ARM平台时,遇到了哪些挑战,你是如何解决的?**

以一个LED驱动为例,先定义一个 file_operations 结构体,接着编写 init 函数,在init函数中完成对管脚的映射,register_chrdev 字符设备的注册,class_create 类的注册,class_device_create 在类下面注册一个设备。exit 函数中完成字符设备的卸载,类的卸载,内存空间的释放。在open函数中完成硬件管脚的初始化,在write函数中完成点灯操作。
file_operations (fai_欧扑锐醒)
register_chrdev (ruai 急死特_磕带屋)
class_create (亏A特)
class_device_create(低畏死_亏A特)
exit (挨可谁特)

**回答:**
在智能家居项目中,我负责编写了LED、SR04、DHT11、SR501等硬件模块的驱动代码。首先,我参考了imx6ul开发板的硬件手册和Linux内核文档,了解了每个硬件模块的寄存器配置和通信协议。然后,我使用C语言编写了这些模块的驱动代码,并通过Linux内核模块的方式加载到系统中。

在调试过程中,我使用了printk函数在内核日志中输出调试信息,帮助我定位和解决问题。此外,我还使用了示波器和逻辑分析仪来检查硬件信号的正确性,确保驱动代码与硬件模块的正常通信。

在将Qt移植到ARM平台时,我遇到的主要挑战是交叉编译环境的搭建和Qt库的依赖问题。为了解决这些问题,我首先在Ubuntu上搭建了交叉编译工具链,并配置了Qt的编译选项。然后,我逐步解决了Qt库的依赖问题,确保所有必要的库都能在ARM平台上正确运行。最终,我成功将Qt应用程序移植到了imx6ul开发板上,并实现了预期的功能。

' 面试问题 4:
**你在动量轮平衡自行车项目中提到了PID算法的调试。你能详细说明一下你是如何调试PID参数的,以及在调试过程中遇到了哪些问题,你是如何解决的?**

PID:
假设我们要对无人机进行PID参数调试
KP的越大,螺旋桨转的越快,向上的力就越大,误差也越大,响应速度也越快,产生的震荡幅度也越大.
KD, 会抵消部分KP的升力(也就是向上的力),让其震荡幅度变小
KI, 无人机的误差存在,无人机的升力也一定存在,当无人机接近目标的高度时,最终会和重力平衡,此时无人机将不会再上升,为了解决这个问题,我们需要累积这个误差值,如果长时间无人机得不到修正,我们需要给无人机提供更大的升力,让无人机上升与目标高度一致

**回答:**
在动量轮平衡自行车项目中,我使用了PID算法来控制无刷电机,通过调节动量轮的转速来实现小车的自主平衡。在调试PID参数时,我首先根据经验设置了一组初始参数,然后通过实验逐步调整这些参数,直到系统达到稳定的平衡状态。

在调试过程中,我遇到的主要问题是系统的振荡和不稳定。为了解决这个问题,我首先调整了比例系数(P),减少了系统的振荡。然后,我逐步增加了积分系数(I),以消除系统的静态误差。最后,我调整了微分系数(D),以提高系统的响应速度和稳定性。

为了更直观地观察系统的响应,我使用了串口通信将传感器的实时数据发送到PC端,并使用MATLAB绘制了系统的响应曲线。通过这些曲线,我可以更准确地调整PID参数,最终实现了小车的稳定平衡。

' 面试问题 5:
**你在简历中提到了熟悉SPI、IIC等总线协议。你能详细说明一下你在项目中是如何使用这些总线协议的吗?特别是在硬件与软件交互时,如何确保通信的可靠性?**

**回答:**
在项目中,我使用了SPI和IIC总线协议来实现硬件模块与主控芯片之间的通信。例如,在智能家居项目中,我使用IIC总线与DHT11温湿度传感器进行通信,获取环境数据。在动量轮平衡自行车项目中,我使用SPI总线与MPU6050陀螺仪进行通信,获取小车的姿态数据。

为了确保通信的可靠性,我首先在硬件设计上确保了信号线的正确连接和屏蔽,减少了电磁干扰的影响。在软件层面,我使用了硬件提供的库函数或自行编写了通信协议,确保数据的正确传输和解析。此外,我还实现了错误检测和重传机制,如果在通信过程中出现错误,系统会自动重传数据,确保数据的完整性和可靠性。

在调试过程中,我使用了逻辑分析仪来捕获总线上的信号,检查通信时序和数据是否正确。通过这些手段,我确保了硬件与软件之间的可靠通信。

' 面试问题 6:
**你在简历中提到了熟悉Linux系统编程和常见的模块驱动编写。你能详细说明一下你在Linux环境下是如何编写和调试驱动的吗?特别是在设备树的使用上,你是如何操作的?**

**回答:**
在Linux环境下,我通常使用C语言编写设备驱动。首先,我会根据硬件模块的规格书和Linux内核的文档,了解设备的寄存器配置和通信协议。然后,我会编写驱动代码,并将其编译为内核模块,通过insmod命令加载到系统中。

在调试驱动时,我通常会使用printk函数在内核日志中输出调试信息,帮助我定位问题。此外,我还会使用dmesg命令查看内核日志,检查驱动的加载和运行情况。

在设备树的使用上,我首先会在设备树源文件(.dts)中定义设备的硬件信息,例如寄存器地址、中断号等。然后,我会使用设备树编译器(dtc)将.dts文件编译为设备树二进制文件(.dtb),并将其加载到内核中。通过设备树,内核可以自动识别和配置硬件设备,减少了手动配置的工作量。

在调试设备树时,我通常会使用of_*系列函数来解析设备树中的节点和属性,确保驱动能够正确获取硬件信息。通过这些方法,我能够高效地编写和调试Linux驱动。

' 面试问题 7:
**你在简历中提到了熟悉Qt、Vscode、虚拟机等工具的使用。你能详细说明一下你在项目开发中是如何使用这些工具来提高开发效率的吗?**

**回答:**
在项目开发中,我通常会使用Qt Creator进行Qt应用程序的开发。Qt Creator提供了强大的代码编辑、调试和界面设计功能,能够大大提高开发效率。特别是在界面设计上,Qt Creator的拖拽式设计工具让我能够快速构建用户界面,并通过信号与槽机制实现界面与逻辑的分离。

对于代码编辑,我通常会使用VSCode作为辅助工具。VSCode提供了丰富的插件支持,例如C/C++插件、Git插件等,能够帮助我更好地管理代码和版本控制。此外,VSCode的智能提示和代码格式化功能也大大提高了我的编码效率。

在开发嵌入式系统时,我通常会使用虚拟机来搭建交叉编译环境。通过在虚拟机中安装Ubuntu系统,我可以在同一台机器上进行开发和测试,避免了频繁切换操作系统的麻烦。此外,虚拟机还提供了快照功能,能够帮助我快速恢复到之前的开发状态,减少了调试时间。

通过这些工具的使用,我能够更高效地进行项目开发,确保项目的按时交付。

' 面试问题 8:
**你在简历中提到了熟悉ARM裸机开发。你能详细说明一下你在裸机开发中的经验吗?特别是在没有操作系统的情况下,你是如何管理硬件资源和实现功能的?**

**回答:**
在ARM裸机开发中,我通常会直接操作硬件寄存器来控制外设。例如,在动量轮平衡自行车项目中,我使用STM32F103C8T6作为主控芯片,直接操作GPIO寄存器来控制LED、电机等外设。

在没有操作系统的情况下,我通常会编写一个简单的主循环来管理任务的执行。例如,在主循环中,我会定期读取传感器的数据,并根据这些数据调整电机的转速,实现小车的平衡控制。为了确保系统的实时性,我通常会使用定时器中断来处理时间敏感的任务,例如PWM信号的生成和传感器的数据采集。

在资源管理上,我会手动分配和管理内存,避免内存泄漏和碎片化。此外,我还会使用状态机来管理系统的状态转换,确保系统的稳定运行。

通过这些方法,我能够在裸机环境下高效地管理硬件资源,并实现复杂的功能。

' 面试问题 9:
**你在简历中提到了熟悉Linux命令和网络编程。你能详细说明一下你在项目开发中是如何使用这些技能的吗?特别是在网络编程中,你是如何处理高并发和网络延迟的?**

**回答:**
在项目开发中,我经常使用Linux命令来进行系统管理和调试。例如,我会使用grep命令查找日志文件中的关键信息,使用ps命令查看进程状态,使用netstat命令检查网络连接等。这些命令帮助我快速定位和解决问题,提高了开发效率。

在网络编程中,我通常会使用TCP/IP协议来实现客户端与服务器之间的通信。为了处理高并发,我使用了多线程技术,为每个客户端连接创建一个独立的线程来处理通信。此外,我还使用了线程池技术,避免了频繁创建和销毁线程的开销,提高了系统的性能。

为了减少网络延迟的影响,我使用了非阻塞I/O和异步通信技术。例如,在Qt中,我使用了QTcpSocket的非阻塞模式来处理网络通信,确保系统能够及时响应网络事件。此外,我还使用了数据压缩和分片技术,减少了网络传输的数据量,进一步降低了网络延迟。

通过这些方法,我能够有效地处理高并发和网络延迟,确保系统的稳定性和响应速度。

' 面试问题 10:
**你在简历中提到了熟悉万能表、示波器的使用。你能详细说明一下你在硬件调试中是如何使用这些工具的吗?特别是在调试复杂的电路时,你是如何定位和解决问题的?**

**回答:**
在硬件调试中,我通常会使用万用表来测量电路中的电压、电流和电阻值,确保电路的正常工作。例如,在动量轮平衡自行车项目中,我使用万用表检查了电源模块的输出电压,确保电机和传感器能够正常工作。

对于复杂的电路调试,我通常会使用示波器来观察信号的波形。例如,在调试SPI或IIC通信时,我使用示波器捕获总线上的信号,检查通信时序和数据是否正确。通过观察波形,我可以快速定位通信问题,例如信号抖动、噪声干扰等。

在调试过程中,我还会使用逻辑分析仪来捕获和分析数字信号。例如,在调试嵌入式系统时,我使用逻辑分析仪捕获GPIO引脚的电平变化,检查系统的状态转换是否正确。通过这些工具的使用,我能够快速定位和解决硬件问题,确保系统的稳定运行。

'面试问题 11
你在简历中提到熟悉数据库操作,能否举例说明你在项目中如何使用MySQL数据库?
回答:
在基于Linux的Qt点餐系统项目中,我使用了MySQL数据库来存储用户信息、菜单信息和订单信息。
数据库设计:
我设计了多个数据库表,包括用户表、菜单表、订单表等,确保数据的结构化存储。
在用户表中,我存储了用户的ID、姓名、权限等信息;在菜单表中,我存储了菜品的ID、名称、价格等信息;在订单表中,我存储了订单的ID、用户ID、菜品ID、订单状态等信息。
数据库操作:
在Qt应用程序中,我使用QSqlDatabase类连接到MySQL数据库,并通过QSqlQuery类执行SQL语句。
例如,在用户管理页面中,我使用INSERT语句添加新用户,使用UPDATE语句修改用户信息,使用DELETE语句删除用户,使用SELECT语句查询用户信息。
通过这些工作,我熟悉了MySQL数据库的基本操作,并能够在实际项目中实现数据的增删改查功能。


'面试问题 12
你在简历中提到熟悉Qt移植到ARM的过程,能否详细说明你在项目中如何将Qt应用程序移植到imx6ul开发板上?
还有关于qt移植,需要将qt移植到板子上运行,第一步:准备 qt 库文件和 tslib 库(触摸屏),

交叉编译环境搭建:
首先,我在Ubuntu主机上搭建了交叉编译环境,安装了ARM架构的交叉编译工具链(如arm-linux-gnueabihf-gcc)。
然后,我下载并编译了Qt的源码,配置了交叉编译选项,确保生成的Qt库可以在imx6ul开发板上运行。
Qt应用程序编译:
在主机上,我使用交叉编译工具链编译Qt应用程序,生成ARM架构的可执行文件。
编译过程中,我确保所有的依赖库(如Qt库、第三方库等)都正确链接。
移植到开发板:
将编译好的Qt应用程序和依赖库通过TFTP或NFS等方式传输到imx6ul开发板上。
在开发板上,我设置了环境变量(如LD_LIBRARY_PATH),确保应用程序能够正确找到依赖库。
最后,我通过命令行运行Qt应用程序,确保其能够在开发板上正常运行。
通过这些步骤,我成功将Qt应用程序移植到了imx6ul开发板上,并实现了智能家居系统的交互界面。

rm板子挂载nfs文件系统就可以和ubantu 共享这个文件夹