区别一 :表示文件的方式不同
系统IO采用文件描述符来表示文件
标准IO采用FILE * 来表示文件
区别二 :标准输入 、标准输出 、标准错误输出不同
系统IO 0 1 2 (使用宏定义 )
#define STDIN_FILENO 0 标准输入
#define STDOUT_FILENO 1 标准输出
#define STDERR_FILENO 2 标准错误输出
标准IO stdin stdout stderr (使用FILE * 类型的变量 )
FILE * stdin 标准输入
FILE * stdout 标准输出
FILE * stderr 标准错误输出
区别三 :缓冲区的区别
系统IO没有缓冲 ,所用函数为open () write ()
标准IO带缓冲区 ,所用函数为fopen () fwrite ()
一般来说 :标准IO读写的速度效率要比系统IO要高
如果是打开读写硬件传感器的驱动 ,一般都是使用系统IO
区别四 :系统IO是Linux内核的API接口 ,标准IO是C语言的库函数
因为标准IO使用缓存技术 ,当数据写入时并没有立即把数据交给内核 ,而是先放在缓存区中 ,当缓存区满时 ,会一次性把缓冲区中的数据交给内核 ,这样就减少了内核态与用户态的切换次数 。
而系统IO每写一次数据就要进入一次内核态 ,这样就浪费了大量时间进行内核态与用户态的切换 ,因此用时更长 。
如果为系统IO设置更大的缓冲区 ,它会比标准IO更快 。
系统IO :
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 可读写
O_APPEND 以追加的方式打开文件
O_CREAT 新建文件
O_EXCL 跟O_CREAT配合使用 ,表示如果文件存在就失败退出 ,不存在就新建
O_TRUNC 跟O_CREAT配合使用 ,表示如果文件存在就清空覆盖掉原来的文件 。
标准IO :
r -- 》只读的方式打开文件
r + -- 》可读写的方式
w -- 》可写的方式打开
w + -- 》可读写的方式 ,如果文件不存在就新建 ,存在就清空覆盖掉
a -- 》可写的方式追加
a + -- 》可读写的方式追加
read :
返回值 > 0 : 1 等于给定字节数 。
2 小于给定字节数 ,可能原因如下 :
(1 )在读到小于指定字节数时就已经收到了EOF ,返回已经读到的数据 。
(2 )在读入部分数据时 ,被信号打断 ,返回已经读到的数据 。
返回值 = 0 : 说明读到了文件尾EOF ,或者socket对端关闭 。
返回值 < 0 : 1 fd设定为非阻塞 ,且要被阻塞时立即返回 - 1 。
2 fd设定为阻塞 ,在读数据之前被打断 ,返回 - 1 。
write :
返回值 > 0 : 1 等于给定字节数 。
2 小于给定字节数 ,可能原因如下 :
(1 )底层物理介质上没有足够空间 。
(2 )创建的文件指定了最大字节数 ,不能再往里面添加数据 。
(3 )已经写了部分数据 ,但被中断信号打断 ,返回中断前写入的字节数 。
返回值 = 0 : 如果有设定相应的error ,说明有相对应的失败原因 ;若无 ,则没有任何影响 。
返回值 < 0 : 1 fd设定为非阻塞 ,但是write将会被阻塞的情况下 ,返回 - 1 。
2 fd设定为阻塞 ,在一个字节都还未写入之前被信号打断 ,返回 - 1 。
3 fd是pipe或者socket的文件描述符 ,对端的读端关闭 。
系统IO和标准IO常用的函数的函数名(移远通信面试)
系统IO :
open 、write 、read 、close
lseek (文件偏移改变 )
stat (获取文件属性 )
chmod (修改文件权限 )
access (判断文件是否存在 )
perror (打印分析错误信息 )
dup /dup2 (文件重定向 ,给原来的文件重新分配一个新的文件描述符 )
sprintf (字符串的拼接 )
sscanf (字符串的拆分 )
mmap (内存映射 )
munmap (解除映射 )
标准IO :
fopen 、fread 、fwrite 、fclose
fseek (文件偏移改变 )
ftell (返回当前位置距离起始位置的字节数 )
rewind (将文件偏移挪到起始位置 )
fprintf (按照指定格式 ,把数据合并写入到文件中 )
fscanf (按照指定格式 ,把数据读取出来 )
fgets (按行读取数据 )
feof (判断是否到文件末尾 )返回1则代表到达文件末尾
fflush (主动刷新缓冲区 )
gets /puts /getc /putc /getchar /putchar略
直接进行大文件 /大目录的全部拷贝 ,可能会出现内存开销不够或者由于特殊情况导致拷贝过程突然中断的情况 。
1 应用层 作用 :开发特定的应用程序需要用到其中的部分协议 http协议 (超文本传输协议 ) ftp协议 (文件传输协议 ) telnet (远程登录 )
2 会话层 作用 :建立会话 ,对数据加密 ,解密
3 表示层 作用 :数据的封装 ,解析
4 传输层 作用 :解决数据在网络中传输的问题 tcp协议和udp协议
5 网络层 作用 :解决路由问题 (数据采取何种路径发送出去最优 )ip协议
6 数据链路层 作用 :开发网卡的驱动 (需要深入了解这个层次的协议 )
7 物理层 作用 :网络接口 ,真实的网卡
概念 :多路复用是计算机中用来监测IO口是否有数据可读 ,可写的一种技术
作用 :多路复用是用于监测多个文件描述符的状态
相关接口函数 :
1 int select (int maxfd ,fd_set * readset ,fd_set * writeset ,fd_set * exceptset ,const struct timeval * timeout );//多路复用
2 void FD_ZERO (fd_set * fdset ); //清空集合
3 void FD_SET (int fd , fd_set * fdset ); //将fd加入集合fdset中
4 void FD_CLR (int fd , fd_set * fdset ); //将fd从集合中删除
5 int FD_ISSET (int fd , fd_set * fdset ); //判断fd在不在集合fdset中(在集合中返回1,不在集合中返回0)
使用TCP协议如何获取IP地址及端口号(他人移远通信面试)
首先要申请一个struct sockaddr_in类型的变量 ,用于存放IP地址和端口号 。
这个变量中要用到三个成员 :
1 sin_family成员存放的是IPV4或者IPV6的标记 ;
2 sin_addr .s_addr成员中存放的是本地主机的IP ,使用htonl函数获取 ;客户端获取服务器的IP地址是通过inet_addr函数来获取的 。
3 sin_port成员存放的是端口号 ,使用htons函数获取 ;
随后再创建一个struct sockaddr_in类型的变量来存放对方的IP和端口号 ,创建的这两个变量会在accept和connect函数中使用到 。
TCP协议的三次握手与四次握手(模拟面试、纳斯达面试)
三次握手是建立连接时使用的
第一次 :客户端给服务器发送一个SYN (同步信号 )
第二次 :服务器收到后回复客户端 ,发送ACK (应答信号 )和SYN (同步信号 )
第三次 :客户端收到第二次握手的信号时 ,再一次发送ACK (应答信号 )给服务器
四次握手是断开连接时使用的
第一次 :客户端发送FIN (结束信号 )至服务器
第二次 :服务器收到FIN信号后回复ACK (应答信号 )给客户端
第三次 :服务器在第二次握手后延时一会再一次发送一个FIN (结束信号 )给客户端
第四次 :客户端收到来自服务器的第二第三次握手后 ,发送ACK (应答信号 )给服务器
详细叙述问题 :
三次握手时 ,第二次握手的时候ACK和SYN信号是同时发送的 ,而在四次握手时 ,为什么服务器在接收到客户端的FIN信号后发送回去的ACK和FIN信号要分开并延迟一会分成两次握手发送过去 ?
答 :
因为TCP有一种连接方式叫做半关连接 ,指的是客户端发送了FIN信号表明客户端不再发送数据了 ,但是还能接收数据 ,此时服务器也未必全部数据都已经发送完毕给客户端 ,在这时后服务器可以选择立即发送FIN信号表示关闭 ,也可以选择发送数据结束后再发送FIN信号表示关闭 ,所以服务器的ACK和FIN一般会分为两次发送 ,故导致比三次握手多了一次 。
TCP协议为什么是三次握手而不是二次握手(玄武云 简答)
1 为了防止已经失效的连接请求报文突然又传送到了服务器 ,从而导致不必要的错误和资源的浪费 。
(举例 :若客户端发送的第一个连接请求没有丢失 ,但由于在网络中滞留的时间太长了 ,此时由于客户端久久没有收到确认报文 ,以为服务器没有收到 ,此时重新向服务器发送这条报文 ,此后客户端和服务器通过两次握手完成连接 ,传输数据 ,然后关闭连接 。此时之前滞留的那一次请求因为网络通畅了 ,到达了服务器 ,此时就又一次建立了连接 ,导致不必要的错误和资源的浪费 。)
2 二次握手只能保证单向连接是通畅的 ,因为TCP是一个双向传输协议 ,只有经过三次握手才能保证双向都可以接受到对方发送的数据 。
使用TCP协议进行编程时客户端与服务端各种函数的使用以及实际发生的先后顺序(CVTE一面、海康威视面试)
服务端 : 1 使用socket函数生成套接字
2 利用bind函数绑定IP和端口号
3 使用listen函数进行对客户端的监听
4 使用accept函数接受客户端的连接请求
5 使用recv和send进行收发信息
客户端 : 1 使用socket函数生成套接字
2 利用bind函数绑定IP和端口号 (这一步在客户端中不是必须的 ,若不绑定则linux的内核会自动分配端口绑定 ,UDP同理 )
3 使用connect函数拨号 ,连接服务器 (三次握手 )
4 使用recv和send进行收发信息
顺序 :先运行服务端 ,服务端进行了socket函数 、bind函数 、listen函数后卡在accept函数处 ,等待客户端 ,而客户端运行后 ,运行了socket函数 、bind函数 (可有可无 )、以及connect函数 ,通过connect函数连接服务器 ,此时服务器的accept函数接收到了连接请求 ,不再阻塞 ,进入recv和send的收发信息 ,此时客户端也进入了recv和send函数的收发信息 。
(延伸 :UDP协议 :socket函数与bind函数调用后就可调用他的sendto和recvfrom函数收发信息 ,但是UDP也是能使用connect函数的 ,但它不是进行三次握手 ,而是检查是否存在立即可知的错误 ,例如连接一个不可达到的地址 。)
使用TCP/UDP时,客户端是否要使用bind函数绑定(CVTE一面)
答 :在服务器中是必须要使用bind函数将套接字和IP地址 、端口绑定的 ,但这一步在客户端中不是必须的 ,若不绑定则linux的内核会自动分配端口绑定 。
TCP和UDP的概念、区别以及优缺点(CVTE一面、玄武云 简答、海康威视面试)
TCP是传输控制协议 ,是面向连接的通讯协议 ,通过三次握手建立连接 ,通讯完成时四次握手 ,一般应用在对安全性 、完整性有严格要求的场景 ,如 :FTP 、SMTP 、HTTP等 。
优点 :TCP具有高可靠性 ,确保传输数据的正确性 ,不出现丢失或者乱序
缺点 :TCP相对于UDP速度慢一点 、效率低 ,而且要求系统资源较多 ,每个链接都会占用系统的CPU 、内存等硬件资源 。
UDP是用户数据报协议 ,是面向无连接的通讯协议
优点 :UDP速度快 、操作简单 、要求系统资源较少 。
缺点 :不可靠 ,可能会出现丢包 、乱序 、数据不完整 。
区别 :
1 TCP是面向连接的传输控制协议 ,而UDP是用户数据报协议 ,提供了无连接的数据报服务 。
2 TCP保证了数据的正确性 ,UDP可能会丢包 。
3 UDP具有较好的实时性 ,工作效率比TCP协议高 。
4 每一条TCP连接都是只能一对一的 ,而UDP可以支持一对一 ,一对多 ,多对一和多对多的交互通信 。
5 TCP对系统资源要求较多 ,UDP对系统资源要求较少 。
1 数据传输之前会有三次握手来进行连接 。
2 在数据传输时候 ,有确认 、滑动窗口 、超时重传 、拥塞控制之类机制 。
3 数据传输之后会进行四次挥手断开连接来节约系统资源 。
第二点具体 :
1 应用数据被分割成TCP认为最适合发送的数据块 。
2 TCP给发送的每一个包进行编号 ,接收方对数据包进行排序 ,把有序数据传送给应用层 。
3 校验和 :TCP 将保持它首部和数据的检验和 。这是一个端到端的检验和 ,目的是检测数据在传输过程中的任何变化 。如果收到段的检验和有差错 ,TCP 将丢弃这个报文段和不确认收到此报文段 。
4 TCP 的接收端会丢弃重复的数据 。
5 利用滑动窗口实现流量控制 :TCP连接的每一方都有固定大小的缓冲空间 ,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据 。当接收方来不及处理发送方的数据 ,能提示发送方降低发送的速率 ,防止包丢失 。TCP 使用的流量控制协议是可变大小的滑动窗口协议 。(TCP利用滑动窗口实现流量控制 )
6 拥塞控制 :当网络拥塞时 ,减少数据的发送 。
7 ARQ协议 :也是为了实现可靠传输的 ,它的基本原理就是每发完一个分组就停止发送 ,等待对方确认 。在收到确认后再发下一个分组 。
8 超时重传 :当TCP发出一个段后 ,它启动一个定时器 ,等待目的端确认收到这个报文段 。如果不能及时收到一个确认 ,将重发这个报文段 。
TCP和UDP一次性最多传输多少个字节(网上答案不唯一,不一定对)
一次传输最多1500字节 ,但是 :
1 TCP只能1480字节 ,因为IP数据报首部有20个字节 。
2 UDP只能1472字节 ,因为在IP数据报首部20个字节的基础上 ,UDP数据报首部还有8个字节 ,所以比TCP少8个 。
单播 (点播 ):一对一 。
广播 :一对多 ,发送给局域网所有的主机 。
组播 (多播 ):广播的一种特殊形式 ,一对多 ,发送组播组里面的所有的主机 。
通过 setsockopt函数设定UDP的套接字的作用 :
1 不做任何设置为一对一点播 。
2 广播则是在setsockopt函数中使用SO_BROADCAST的宏定义对套接字进行广播设置 。
3 组播则是在广播的基础上 ,组播端要使用组播地址 (D类地址 :224.0 .0 .0 到239 .255.255 .255 )进行设置 ,接收端要使用该组播地址 ,通过setsockopt函数加入组播组中 ,达到组播的功能 。
ping命令是用来探测本机与网络中另一主机之间是否可达的命令 ,即是否可以进行通信 。
ping命令是通过ICMP协议来工作的 ,在运行的时候ping命令会发送一份ICMP回显请求报文给目标主机 ,并等待目标主机返回ICMP回显应答 ,若源主机在一段时间内收到了目标主机的回显应答 ,则证明两台主机之间网络是可达的 。
ICMP协议是通过IP协议来进行发送的 ,它封装在IP的包中 ,由于IP协议是一种无连接 、不可靠的数据包协议 ,它并不能保证数据一定被送达 ,而ICMP协议就是为了保证数据送达而引入的模块 。
浅显一点的说法就是当传送的IP数据包发送异常的时候 ,ICMP就会将异常信息封装在包中 ,然后回传给源主机 ,随后源主机得到该异常信息后会对发送异常的数据再一次发送 。
1 DNS协议 ,将域名转换为IP地址 ,用到了DNS
2 ARP协议 ,获取IP地址后 ,通过ARP解析服务获得MAC地址
3 ICMP协议 ,ping功能测试另一台主机与本机是否互相可达 ,程序发送一份ICMP回显请求给目标主机 ,并等待返回ICMP回显应答 。
不会用到TCP ,因为没有涉及到数据的传输 。
linux系统中文件的类型及其标志(CVTE笔试)
下列例子权限全为777
- rwxrwxrwx 普通文件 ,开头为 - (普通文件 )
drwxrwxrwx 目录文件 ,开头为d (文件夹 )
crwxrwxrwx 字符设备文件 ,开头为c (用于驱动开发 ,对应硬件设备 ,实际上并不存在与磁盘中 ,是文件系统虚拟出来的 )
brwxrwxrwx 块设备文件 ,开头为b (用于驱动开发 ,对应硬件设备 ,实际上并不存在与磁盘中 ,是文件系统虚拟出来的 )
lrwxrwxrwx 符号链接文件 ,开头为l (类似于windows的快捷方式 ,分为软链接和硬链接 ,具体区别看笔记 )
srwxrwxrwx 套接字文件 ,开头为s (用于进程间通信 ,实际上是网络通信 ,具体例子有三次握手和四次握手 )
prwxrwxrwx 管道文件 ,开头为p (用于进程间的通信 ,一般是由有名管道产生 )
字符设备 :
1 一个字节一个字节读写的设备 。
2 读取数据需要按照先后数据 (顺序读取 )。
3 常见的字符设备有鼠标 、键盘 、串口 、控制台和LED设备 。
4 每个字符设备在 /dev目录下对应一个设备文件 ,linux用户程序通过设备文件 (或称设备节点 )来使用驱动程序操作字符设备 。
块设备 :
1 数据以固定长度进行传输 ,比如512K 。
2 从设备的任意位置 (可跳 )读取 ,但实际上 ,块设备会读一定长度的内容 ,而只返回用户要求访问的内容 ,所以随机访问实际上还是读了全部内容 。
3 块设备包括硬盘 、磁盘 、U盘和SD卡等 。
4 每个块设备在 /dev目录下对应一个设备文件 ,linux用户程序通过设备文件 (或称设备节点 )来使用驱动程序操作块设备 。
5 块设备可以容纳文件系统 ,比如磁盘 。
线程的相关函数的函数名(移远通信面试、扬智科技面试)
线程的创建 pthread_create
线程的退出 pthread_exit
线程的等待回收 pthread_join
线程的取消 pthread_cancel
作用 :协调多个线程对于共享资源的访问 ,保证任意时刻只有一个线程在访问共享资源 (临界区资源 )
原理 : 通过上锁和解锁操作 ,实现任意时刻只有一个线程在访问共享资源 ,某个线程如果对共享资源上锁了 ,那么其他线程都无法使用该共享资源 ,只有等到解锁以后其他线程才能使用共享资源
特点 :
多个线程使用同一把锁 ,谁先上锁 ,那么后面上锁的线程都会阻塞
死锁 :
定义 :互斥锁上完锁以后没有对应的解锁操作 ,导致这把锁任何线程都无法使用 。
产生死锁常见的原因 :
1 程序员粗心大意 ,忘记解锁
解决方法 :
把解锁操作代码加上即可
2 线程刚上锁 ,还没有来得及解锁就被取消了
解决方法 :
(1 )可以把线程设置成不能被取消
(2 )使用pthread_cleanup_push函数 ,设置线程取消的时候去调用解锁操作 。(注意 :pthread_cleanup_push要和pthread_cleanup_pop配合使用 ,因为这两个函数其实是宏定义 ,push包含左花括号 ,pop包含右花括号 ,必须成对使用 )
特点 :不能单独使用 ,需要配合互斥锁实现线程的协调控制
作用 :满足某个条件的时候 ,可以用条件变量去阻塞或者唤醒线程 (顾名思义 ,就是满足某个条件之后 ,利用条件变量去阻塞 /解除阻塞线程 )
1. 本质 : 多个线程组成的一个集合 ,目的为了并发执行任务 ,定义时是一个结构体 ,成员有线程数量 ,线程ID ,互斥锁 ,条件变量 ,任务数量 ,结束标志位等 。
2. 线程池的关键技术
(1 )万能函数指针 (通用函数指针 ):
void * (* p )(void * )
注意事项 :函数的参数个数超过1个 -- 》参数打包成结构体 ,多个参数就变成一个参数 (全部包含在结构体里面了 )
原理 :该函数需要用到互斥锁在完成一个任务后减少任务数量 ,解锁后继续下一个任务 ,还要用到条件变量在任务全部完成时通过判断任务数量和结束标志位退出 。
(2 )封装线程池有关的接口函数
三大基本函数需要我们去封装
第一个 :初始化线程池
原理 :通过对线程池结构体中的成员初始化让线程池进入工作模式 ,随后使用循环创建对应数量的线程 。
第二个 :添加任务
原理 :通过动态分配内存准备新的内存空间分配给新的任务 ,在添加任务时利用互斥锁上锁防止任务函数完成任务时减少任务数量带来的冲突 ,将任务尾插到任务链表中 ,并且对线程池结构体中各个成员变量进行更新 。
第三个 :线程池的销毁 (回收线程 ,想办法让线程的任务函数退出 )
原理 :通过改变线程池结构体成员变量中的结束标志位 ,令所有线程能够退出 ,随后在任务函数中开始退出线程 ,并在这个线程池销毁函数中回收所有线程 。
另外两个接口函数 :增加线程 ,删除线程 。
只适用于linux系统 ,它是linux中专用的一种链表 (本质上就是个双向循环链表 )
优势 : 1 内核链表把指针操作封装成了函数或者宏定义 (方便你直接使用 ,隐藏了指针操作的细节 )
2 数据和指针做了剥离 ,数据域由程序员自己来定义需要的数据类型 ,指针域不需要程序员来操心 ,linux已经定义了一个结构体struct list_head专门用来存放两个指针 (next ,prev )。
linux还把这两个指针相关的操作 (增删改查 )都封装成了函数或者宏定义 ,给程序员直接调用 。(比如初始化头结点 :INIT_LIST_HEAD (x ),尾插 :list_add_tail (x ,x ),删除 :list_del (x ),遍历 :list_for_each_entry (x ,x ,x ))
1 ARM指令集
2 Thumb指令集 (是ARM体系结构的变种 ,带来了更高的代码密度 )
Cortex-A53有多少个核心(GEC6818开发板)(扬智科技面试)
核心数量 :1 - 4 个
虚拟地址 :48 位
物理地址 :40 位
页表大小 :4 K ,16 K ,64 K ,1 M ,2 M ,1 G
作用 :uboot 属于bootloader的一种 ,是用来引导启动内核的 ,它的最终目的就是 ,从flash中读出内核 ,放到内存中 ,启动内核 。
过程 :
第一阶段 :硬件的初始化
第二阶段 : 1 从flash中读出内核
2 移动内核到合适的加载位置
3 启动内核
1 内核指的是一个提供硬件抽象层 、磁盘及文件系统控制 、多任务等功能的系统软件 。
2 内核是一个操作系统的核心 ,是操作系统最基本的部分 。它负责管理系统的进程 、内存 、设备驱动程序 、文件和网络系统等 ,决定着系统的性能和稳定性 。
3 直接对硬件操作是非常复杂的 ,所以内核通常提供一种硬件抽象的方法来完成这些操作 。硬件抽象隐藏了复杂性 ,为应用软件和硬件提供了一套简洁 ,统一的接口 ,使程序设计更为简单 。
1 进程管理 :
内核负责创建和销毁进程 , 并处理它们与外部世界的联系 (输入和输出 ),不同进程间通讯 (通过信号 ,管道 ,或者进程间通讯原语 )对整个系统功能来说是基本的 ,也由内核处理 。 另外 , 调度器 , 控制进程如何共享CPU ,是进程管理的一部分 。更通常地 ,内核的进程管理活动实现了多个进程在一个单个或者几个CPU 之上的抽象 。
2 内存管理 :
计算机的内存是主要的资源 , 处理它所用的策略对系统性能是至关重要的 。内核为所有进程的每一个都在有限的可用资源上建立了一个虚拟地址空间 。内核的不同部分与内存管理子系统通过一套函数调用交互 ,从简单的malloc /free对到更多更复杂的功能 。
3 文件管理 :
Linux 在很大程度上基于文件系统的概念 ;几乎Linux中的任何东西都可看作一个文件 。内核在非结构化的硬件之上建立了一个结构化的文件系统 ,结果是文件的抽象非常多地在整个系统中应用 。另外 ,Linux 支持多个文件系统类型 ,就是说 ,物理介质上不同的数据组织方式 。例如 ,磁盘可被格式化成标准Linux的ext3文件系统 ,普遍使用的FAT文件系统 ,或者其他几个文件系统 。
4 驱动管理 :
几乎每个系统操作终都映射到一个物理设备上 ,除了处理器 ,内存和非常少的别的实体之外 ,全部中的任何设备控制操作都由特定于要寻址的设备相关的代码来进行 。这些代码称为设备驱动 。内核中必须嵌入系统中出现的每个外设的驱动 ,从硬盘驱动到键盘和磁带驱动器 。
5 网络管理 :
网络必须由操作系统来管理 ,因为大部分网络操作不是特定于某一个进程 : 进入系统的报文是异步事件 。报文在某一个进程接手之前必须被收集 ,识别 ,分发 ,系统负责在程序和网络接口之间递送数据报文 ,它必须根据程序的网络活动来控制程序的执行 。另外 ,所有的路由和地址解析问题都在内核中实现 。
Linux用户空间和内核空间 用户态和内核态(扬智电子简答)
用户空间 (用户态 ):用户程序的运行空间 。为了安全 ,它们是隔离的 ,即使用户的程序崩溃 ,内核也不会受到影响 。只能执行简单的运算 ,不能直接调用系统资源 ,必须通过系统接口 (system call ),才能向内核发出指令 。
内核空间 (内核态 ):Linux内核的运行空间 ,可以执行任意命令 ,调用系统的一切资源 。
Linux用户态和内核态切换三种方式(扬智电子简答延伸)
1 系统调用 :这是用户态进程主动要求切换到内核态的一种方式 。用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作 。例如fork ()就是执行了一个创建新进程的系统调用 。系统调用的机制和新是使用了操作系统为用户特别开放的一个中断来实现 ,所以系统调用本身就是中断 ,但是软件中断 ,跟硬中断不同 。
2 异常 :如果当前进程运行在用户态 ,如果这个时候发生了异常事件 ,就会触发切换 。例如 :缺页异常 。
3 外设中断 :当外设完成用户的请求时 ,会向CPU发送中断信号 。这时CPU会暂停执行下一条即将要执行的指令而转到与中断信号对应的处理程序去执行 ,如果前面执行的指令时用户态下的程序 ,那么转换的过程自然就会是 由用户态到内核态的切换 。如硬盘读写操作完成 ,系统会切换到硬盘读写的中断处理程序中执行后边的操作等 。
实际上最终是中断机制实现的
Linux系统中资源分配、资源调度的最小单位(移远通信面试)
1 进程是操作系统分配资源的最小单位
资源调度的最小单位是进程
2 线程是操作系统调度的最小单位
CPU调度的最小单位是线程
1 根本区别 :进程是操作系统资源分配的基本单位 ,而线程是处理器任务调度和执行的基本单位 。
2 资源开销 :每个进程都有独立的代码和数据空间 (程序上下文 ),程序之间的切换会有较大的开销 ;线程可以看做轻量级的进程 ,同一类线程共享代码和数据空间 ,每个线程都有自己独立的运行栈和程序计数器 (PC ),线程之间切换的开销小 。
3 包含关系 :如果一个进程内有多个线程 ,则执行过程不是一条线的 ,而是多条线 (线程 )共同完成的 ;线程是进程的一部分 ,所以线程也被称为轻权进程或者轻量级进程 。
4 内存分配 :同一进程的线程共享本进程的地址空间和资源 ,而进程之间的地址空间和资源是相互独立的 。
5 影响关系 :一个进程崩溃后 ,在保护模式下不会对其他进程产生影响 ,但是一个线程崩溃整个进程都死掉 。所以多进程要比多线程健壮 。
6 执行过程 :每个独立的进程有程序运行的入口 、顺序执行序列和程序出口 。但是线程不能独立执行 ,必须依存在应用程序中 ,由应用程序提供多个线程执行控制 ,两者均可并发执行 。
进程的相关函数的函数名(移远通信面试延伸、扬智科技面试延伸)
进程的创建 fork ()/vfork ()
进程的退出 exit (int status )/_exit (int status )
进程的等待回收 wait (int * stat_loc )/waitpid (pid_t pid , int * stat_loc , int options )
获取进程的id以及获取父进程的ID getpid (void )/getppid (void )
获取当前进程所在进程组的ID getpgrp (void )
创建或加入其他进程组 setpgid (pid_t pid , pid_t pgid )
概念 :物理层面上只有一个CPU ,但是我们要让每一个进程都认为自己在使用CPU ,都认为自己拥有整个内存空间 ,这就是进程的虚拟化 。通过地址空间实现 。
过程 :进程从硬盘加载到内存 ,操作系统为其创建task_struct (进程控制块 )和mm_struct (虚拟地址 )并且用指针把二者关联起来 。然后虚拟地址通过页表和物理内存建立关系 。
地址空间 :本质上是描述进程所占有空间的一张表 ,存在于操作系统内核中 ,是一种数据结构 ,该数据结构把内存分成若干个区域 。
每一个进程都有一个地址空间 。虚拟地址空间中 ,该虚拟地址空间暴露给上层的地址叫做虚拟地址 ,在进程运行时 ,通过页表映射转换到实际内存中 ,然后得到对应的代码和数据 。
设计出虚拟地址和物理地址的好处(扬智科技面试延伸)
1 既然每个进程的内存空间都是一致而且固定的 ,所以链接器在链接可执行文件时 ,可以设定内存地址 ,而不用去管这些数据最终实际的内存地址 ,这是有独立内存空间的好处 。
2 当不同的进程使用同样的代码时 ,比如库文件中的代码 ,物理内存中可以只存储一份这样的代码 ,不同的进程只需要把自己的虚拟内存映射过去就可以了 ,节省内存 。
3 在程序需要分配连续的内存空间的时候 ,只需要在虚拟内存空间分配连续空间 ,而不需要实际物理内存的连续空间 ,可以利用碎片 。
1 SCHED_OTHER 分时调度策略 。(非实时调度策略 )
2 SCHED_FIFO 实时调度策略 ,先到先服务 。一直运行到有更高优先级的任 务或者自己放弃 ,若遇到相同优先级的任务 ,则要等进程运 行完毕或者主动放弃才会运行另一个相同优先级的任务 。
3 SCHED_RR 实时调度策略 ,时间片轮转 。给进程分配时间片 ,每个进程 运行完对应的时间片后 ,系统重新分配时间片 。
若同时有实时调度策略和分时调度策略 :实时调度策略的进程准备好后会立即抢占非实时的进程 。
1 就绪态 :进程已经运行了 ,但是还没有获取cpu的使用权 ,当cpu的调度算法切换到该进程 ,那么进程就能转入到执行态 。
2 执行态 :进程已经运行 ,并且cpu的使用权也获取了 。
3 睡眠态 :使用了sleep ()等等带有阻塞功能的函数 (pause ()、wait ()、waitpid ()),进程都会进入睡眠态 。
4 暂停态 :进程收到暂停信号 。
5 僵尸态 :进程退出了 ,没有回收就变成僵尸态 。
6 死亡态 :进程退出了 ,有回收 。
1 用户ID号和用户组号
2 环境变量
3 堆栈
4 共享内存
5 打开文件的描述符
6 执行时的关闭标志
7 信号控制设定 (Signal )
8 进程组号
9 当前工作目录
10 根目录
11 文件方式创建屏蔽字
12 资源限制
13 控制终端
1 进程号
2 属于子进程的父进程的进程号
3 自己的文件描述符和目录流的拷贝
4 子进程的进程正文 、数据和其他锁定内存
5 异步输入和输出
(一般来说 ,回答文件锁和进程号即可 )
作用 :实现在一个进程中调用执行另外一个进程
用法 :
1 system用法 ,函数system ()
2 exec用法 ,各种exec族的函数 ,常用execl ()(例子 :execl ("/bin /ls ","ls ","- l ",NULL);该代码功能和shell中的" ls - l "功能一样)
system和exec函数族区别 :
执行完exec函数之后 ,它会取代原调用进程的数据段 、代码段和堆栈段 。在执行完之后 ,原调用进程的内容除了进程号外 ,其他全部都被替换了 。看到的现象就是exec函数后面的代码没有办法执行了 (因为被取代了 );system则是执行完以后后面的代码也能够正常执行 。
原理 :多个进程申请同一块内存区域 ,用于通信
使用共享内存出现的问题 :
创建失败 ,文件已经存在
原因 :共享内存创建过一次 ,下次就不能重复创建了
解决方法 :
1 不靠谱的方法 ,手动删除 ,下次还要手动删除
2 判断共享内存创建失败的原因 ,如果是由于已经存在 ,我就直接打开共享内存 ,不新建了 ,使用errno判断
使用 :
1 申请共享内存 ,使用shmget ()函数申请共享内存
2 映射得到共享内存的首地址 ,使用shmat ()函数
3 使用完后解除映射 ,使用shmdt ()函数
4 删除共享内存 、设置 /获得共享内存的属性 ,使用shmctl ()函数
作用 :协调多个进程对于共享资源 (临界区资源 )的访问 ,进程1在访问共享资源的时候 ,其它进程不允许访问 ,确保任意时刻只有一个进程在访问共享资源确保某个进程在某个时刻对于共享资源具有独占性
使用 :
1 申请信号量 :semget ()函数申请信号量
2 对信号量进行P操作和V操作 ,即对信号量进行加减 。我们是对Linux内核中自带的结构体struct sembuf中的成员sem_op赋予正负值来进行PV操作
加法操作 (V操作 ) - - 》把信号量增加
减法操作 (P操作 ) - - 》把信号量减少
3 使用semctl ()函数对想要所用的信号量进行赋值初始化 、获取信号量 、删除信号量这几种操作 。
特点 :
1 信号量的值不能为负数
2 信号量的值如果变成0 ,你还要进行p操作 ,会阻塞当前进程
特点 :
1 生命周期随内核 ,消息队列会一直存在 ,需要我们使用接口函数删除或者使用命令删除 。
2 消息队列可以双向通信 (通信双方可以选择性的接收信息 ,通过所谓的消息类型去区分不同的消息 )
3 克服了管道只能承载无格式字节流的缺点
4 如果接收的消息类型不存在 ,会阻塞
5 如果消息队列中有多条类型一样的消息 ,接收的时候只能按照发送的先后顺序接收 (队列的特点 )
不足 :
每个消息有一个最大的长度 ,即有长度上限
使用 :
1 申请消息队列 ,使用msgget ()函数申请消息队列
2 使用消息队列收发信息 ,使用函数msgsnd ()函数 ,需要将发送的信息打包成结构体 ,结构体内包含消息的类型和真实的信息 ,随后才可收发信息
3 删除消息队列 ,设置 ,获取消息队列的属性 ,使用msgctl ()函数 ,通过消息队列的ID号进行对应的删除等操作 。
无名管道 :(int pipe (int fildes [2 ]);读是fildes [0 ],写是fildes [1 ])
创建时会得到分别用来进行读取数据和写入数据的文件描述符
特点 :
1 无名管道有固定的读写端 ,不能弄错
2 如果无名管道没有进程写入数据 ,那么read读取管道信息会阻塞
3 只能用于具有父子 ,兄弟关系的进程之间通信
有名管道 :(int mkfifo (const char * pathname , mode_t mode );)
会得到一个管道文件用来进行进程之间的通信 。和无名管道一样使用系统IO的函数 ,如 :open 、read 、write等 。
特点 :
1 有名管道没有固定的读写端
2 如果有名管道没有进程写入数据 ,那么read读取管道信息会阻塞
3 有名管道的适用范围更广 ,既能用于父子 ,兄弟进程之间通信 ,也能用于没有任何血缘关系进程间通信
4 有名管道只能在纯粹的linux环境中创建
不足 :
有名管道只用于通信 ,不保存数据 (例如 :进程1写入内容到管道中然后退出 ,运行进程2读取内容是读取不了的 )
使用有名管道出现的问题 :
创建有名管道失败 !
报错为 :
File exists
原因 :
上一次运行程序 ,已经创建过一个管道文件 ,不能重复创建
解决方法 :
判断管道文件是否存在 ,存在我就直接打开 ,不存在我就创建 ,然后打开
Linux中定义了62个信号 ,从1号到64号 ,其中32号和33号没有定义 。
1 到31号是一组 ,名为非实时信号 ,可以嵌套响应 (进程在响应A信号的时候 ,B信号发送过来 ,进程会立马响应B信号 ,B信号响应完了 ,在回到A信号继续响应 )。
34 到64好是另外一组 ,叫做实时信号 ,不可以嵌套响应 。
重要编号 :
2 号 SIGINT 等于Linux中的ctrl + c
18 号 SIGCONT 让进程继续执行
19 号 SIGSTOP 让进程暂停执行
linux中信号四种响应方式
1 信号的默认动作响应 ,linux已经规定好了所有信号的默认动作 ,绝大部分默认动作都是终止进程
2 改变信号的响应动作 ,signal /sigaction函数去捕捉信号并改变 。注意 :有两个信号SIGKILL和SIGSTOP很特殊 ,不能改变它们的默认动作
3 忽略信号 (signal (信号 ,SIG_IGN );)左耳进右耳出 ,当信号发送过来的时候 ,进程当它不存在 ,直接舍弃该信号 (不存在了 )。
注意 :有两个信号SIGKILL和SIGSTOP很特殊 ,不能忽略 。
4 信号的阻塞 (信号的屏蔽 ,定义阻塞掩码集 ,将信号加入进去 ,即可阻塞信号 ),阻塞信号仅仅只是把信号挡在进程的外面 (暂时挂起 ),不去响应 ,等到解除阻塞 ,依然能够正常响应信号 (信号依然存在 )。
注意 :有两个信号SIGKILL和SIGSTOP很特殊 ,不能阻塞
1 任何一个进程删除该文件时 ,另外一个进程会立即出现读写失败 。
2 有缓冲的情况下 ,一个进程对文件长度和内容进行修改 ,另外一个进程可以立即感知 。
3 多个进程中分别产生生成多个独立的fd 。
4 多个进程可以分别读取文件的不同部分而不会相互影响 。
1 、指向不可访问的地址
危害 :触发段错误 。
2 、指向一个可用的 ,但是没有明确意义的空间
危害 :程序可以正确运行 ,但通常这种情况下 ,我们就会认为我们的程序是正确的没有问题的 ,然而事实上就是有问题存在 ,所以这样就掩盖了我们程序上的错误 。
3 、指向一个可用的 ,但是正在被使用的空间
危害 :如果我们对这样一个指针进行解引用 ,对其所指向的空间内容进行了修改 ,但是实际上这块空间正在被使用 ,那么这个时候变量的内容突然被改变 ,当然就会对程序的运行产生影响 ,因为我们所使用的变量已经不是我们所想要使用的那个值了 。通常这样的程序都会崩溃 ,或者数据被损坏 。
i ++ 是先使用i的值 ,再i + 1
++ i是先i + 1 ,再使用i的值
底层原理 :
1 i ++ 和 ++ i都是带有返回值的函数
2 i ++ 返回的是一个常量值
3 ++ i返回的是i的引用 /指针
例子 :若i = 1 ,则 (++ i )+ (++ i )的值为6 ,因为第一个 ++ i时 ,i变为2 ,第二个 ++ i时 ,i变成3 ,则最终为3 +3 得6 。
sizeof是一个运算符 ,能够计算出变量的字节大小 ,对于字符串来说 ,sizeof会把字符串最后的那个 '\0' 字节也计算进去 。strlen是C语言库里提供的一个计算字符串长度的函数 ,遇到 '\0' 就会结束 ,所以并不是适用于所有的情况 。
new/malloc delete/free的区别(模拟面试延伸)
1 new从自由存储区上分配内存 ,malloc从堆上分配内存 。(自由存储区是堆与静态存储区 )
2 new 、delete返回的是对应的数据类型指针 ,malloc和free返回的是void * 类型 。
3 使用new申请内存时无须指定申请的内存块的大小 ,malloc则必须指出所需内存的尺寸 。
4 new可以调用对象的构造函数 ,delete可以调用析构函数 ,而malloc和free仅只能分配内存和回收内存 。
5 new 、delete是运算符 (操作符 ),malloc 、free是函数 。
内存泄漏 :一直申请堆空间 ,但没有释放 ,导致堆内存越来越少 ,最后没有可以使用的内存 。
堆内碎块 :频繁地分配和释放不同大小的堆空间会导致堆内碎块 。
预防内存泄漏 :
1 养成良好的代码习惯 ,保证malloc /new和free /delete匹配 。
2 有专门的判断代码中有无内存泄漏情况的软件 。
预防堆内碎块 :
1 在进程启动时 ,使用 int mallopt (int param , int value ) 函数显式地设置 M_MMAP_THRESHOLD 的值为128K ,关闭M_MMAP_THRESHOLD动态调整特性
2 优化应用程序中内存管理方式 ,不要频繁申请 、释放内存空间 ,减少内存碎片 。
作用 :修饰易变的变量 ,告诉编译器这个变量的值短时间内会改变很多次 ,不要去优化该变量 。系统每次使用这个被修饰的变量时 ,都是直接从对应的内存当中提取 ,而不会利用存在cache当中的原有数值 ,以适应它未知何时会发生的变化 ,系统对这种变量的处理不会做优化 。
例如 :
1 并行设备的硬件寄存器
2 一个中断服务子程序中会访问到的非自动变量
3 多线程应用最后那个被几个任务共享的变量
C语言中 :
1 修饰局部变量
2 修饰全局变量
3 修饰函数
C ++ 中 :
1 修饰类中的方法
2 修饰类中的属性
1 修饰普通变量 ,让变量的读写属性变为只读 ,即常说的常量 ,不能修改 ,可以访问 。
2 修饰指针
(1 )指针常量 :int * const p ;不能修改p的指向 ,可以修改 * p的值 。
(2 )常量指针 :int const * p ;不能修改 * p的值 ,可以修改p的指向 。
3 C ++ 中修饰类的成员函数 ,表示该函数只能对类中的成员属性进行读操作 ,不能改变类中的成员属性 。
一个参数既可以被const修饰也能被volatile修饰吗?
可以 ,volatile修饰是因为它可能被意想不到的改变 ,const修饰是因为程序不应该试图去修改它 。(例子 :只读的状态寄存器 )
1. 什么是内联函数
关键字 :inline
inline 函数的返回值 函数名 (形参 )
{}
2. 内联函数的作用
解决函数调用时入栈和出栈的时间耗费 ,对于经常要使用的 ,短小的代码适合定义成内联函数
内联函数的原理 :编译器会在调用的位置直接把内联函数的源码复制过来
优点 :节约了函数调用入栈出栈的时间耗费 ,内联函数用内联代码替换函数调用
内联函数体的代码不能过长 ,因为内联函数省去调用函数的时间是以代码膨胀为代价的 。内联函数不能包含循环语句 ,因为执行循环语句要比调用函数的开销大 。
缺点 :增加主程序的长度 ,增加了主函数占用的内存 ,耗费了比较多的内存
3. 什么时候使用内联函数
函数代码量很小 ,并且需要反复使用 -- 》定义成内联
C语言与C++中NULL的值是多少?(通则康威简答延伸)
C语言中空指针是NULL ,但并没有规定NULL必须是0 ,但是C ++ 中空指针必须是0值 。
原因 :C ++ 为了支持模版泛型编程 ,强行规定了所有类型都必须有一个0值 ,而指针0值就是空指针 ,此时访问NULL就等于访问0地址 。C语言没有这样规定 ,所以可以使用任意值表示NULL ,但一般情况下NULL在实际底层调用时就是0 ,所以也可以看作NULL和0的值都是一样的 。
1 、用循环进行替换 (非递归方法实现 )
2 、限制递归次数
3 、若必须要递归又不允许限制递归次数 ,则使用 “异步任务队列 ”。这样就可以达到将递归函数从执行栈中转移到异步队列中 ,从而减轻执行栈压力的目的 。
1 封装
一个对象封装自己的属性和方法
优点 :
1 、良好的封装可以减少耦合
2 、类内部的结构可以自由修改
3 、可以对成员进行更精确的控制
4 、隐藏信息 ,实现细节
2 继承
使用已存在的类作为基础建立新的类 ,新的类可以增加新的数据或者功能 ,也可以使用父类的功能 ,但不能选择性继承父类
缺点 :
1 、父类改变 ,子类就会跟着改变
2 、继承破坏了封装
3 、继承是一种强耦合关系
3 多态
一个引用变量倒底会指向哪个类的实例对象 ,该引用变量发出的方法调用到底是哪个类中实现的方法 ,必须在由程序运行期间才能决定
优点 :
不修改程序代码就可以改变程序运行时所绑定的具体代码 ,让程序可以选择多个运行状态 ,这就是多态性 。
C ++ 具有继承关系的类中 ,它们都具有相同名字的方法 ,这种情况就是多态 。
分类 :
编译时多态 :函数重载
运行时多态 :父类的指针 ,引用指向不同的子类对象
虚函数就是为了多态而产生的 。
多态的特点和要求
1 必须要有继承 ,没有继承 ,就没有多态
2 子类必须要重写父类的同名方法
3 父类的同名方法必须定义成虚函数
注意 :父类的同名方法定义成了虚函数 ,所有子类中同名的方法全部都默认是虚函数 (子类加不加virtual都行 )
底层原理 :
1 虚函数表 (虚表 ):C ++ 中专门用来存放虚函数地址的一种数据结构 (本质是个存放函数地址的数组 )。
2 一个类中定义了虚函数 ,那么这个类以及它派生出来的子类都会有各自独立的虚函数表 ,该类所有的对象中会新增一个指针 ,该指针用来指向虚表的首地址父类的指针或者父类的引用去调用方法的时候 ,其实就是去查询虚函数表
1 函数的返回值是该类对象自身的时候 ,如下例 :
Cat & cmpAge (Cat & other )
{if (other .age > age ) return other ;
else return * this ;}
2 类中方法用到了对象自身的情况
普通继承跟虚继承的区别
第一 :虚继承可以解决二义性和A被构建多次这两个问题 ,普通继承不能解决 。
第二 :只要一个类虚继承了其它类 ,那么该类所有的对象中都会新增一个指针 ,该指针专门用来指向系统中虚基类表的首地址 。
虚基类表 :C ++ 中专门用来存放虚基类地址的一种数据结构 。
什么时候用 : 环状继承除了头尾 ,中间层使用虚基类 ,若不用则会导致 :二义性 (即环装继承中最后一个子类调用最原始的父类的函数时无法确认最后一个子类调用了哪一个第二级的父类中最原始父类的副本中的函数 )和A被构建多次 。
在C ++ 中 ,类的静态成员 (static member )必须在类内声明 ,在类外初始化 ,像下面这样 。
class A
{
private:
static int count ; // 类内声明
};
int A ::count = 0 ; // 类外初始化,不必再加static关键字
因为静态成员属于整个类 ,而不属于某个对象 ,如果在类内初始化 ,会导致每个对象都包含该静态成员 ,这是矛盾的 。
能在类中初始化的成员只有一种 ,那就是静态常量成员 。
此外 ,还需要注意的是声明和定义的区别 :
①变量定义 :用于为变量分配存储空间 ,还可为变量指定初始值 。程序中 ,变量有且仅有一个定义 。
②变量声明 :用于向程序表明变量的类型和名字 。
在该题目中的定义指的是分配空间 。
1 初始化 :引用在定义时必须初始化 ,指针则没有要求 (尽量初始化 ,防止野指针 )
2 引用在初始化引用一个实体后 ,就不能再引用其它实体 ,而指针可以在任意时候指向一个同类型实体
3 没有NULL引用 ,但是有nullptr指针
4 在sizeof中含义不同 : 引用结果为引用类型的大小 ,但指针始终是地址空间 ,所占字节个数 (32 位平台占4个字节 )
5 引用自加即引用的实体增加1 ,指针自加即指针向后偏移一个类型的大小
6 有多级指针 ,但没有多级引用
7 访问实体的方式不同 ,指针需要显式解引用 ,引用编译器自己处理
8 引用比指针使用起来相对安全
1 管理方式不同 。栈由操作系统自动分配释放 ,无需我们手动控制 ;堆的申请和释放工作由程序员控制 ,容易产生内存泄漏 。
2 空间大小不同 。每个进程拥有的栈的大小要远远小于堆的大小 。
3 生长方向不同 。堆的生长方向向上 ,内存地址由低到高 ;栈的生长方向向下 ,内存地址由高到低 。
4 分配方式不同 。堆都是动态分配的 ,没有静态分配的堆 。栈静态分配是由操作系统完成的 ,比如局部变量 ,而动态分配是由操作系统进行释放 ,无需我们手动释放 。
5 分配效率不同 。栈由操作系统自动分配 ,有专门的指令执行 ,效率比较高 ;堆则是由库函数或者运算符申请 ,机制比较复杂 ,效率比栈低得多 。
6 存放内容不同 。栈存放的内容 ,是函数返回地址 、相关参数 、局部变量和寄存器内容等 ;堆中具体存放内容是由程序员来填充的
缓冲区:计算机内存中一块特殊的区域 ,这个区域专门留给scanf /printf来使用
底层原理:用户从键盘输入的任何数据 ,都会被当成字符串存放到系统的IO缓冲区里面
scanf依据程序员写的格式控制符 ,从IO缓冲区里面读取合适的字符串 ,然后把这个字符串转换成你需要的数据类型
问题 :scanf读取数据 ,如果用户不配合 ,输入的数据类型不正确 ,该怎么办 ?
解决方法 :scanf的返回值表示用户从键盘输入的符合要求的数据类型个数
底层原理 :打印的数据先存放到IO缓冲区
代码中遇到如下几种情况会自动刷新缓冲区 (所谓的自动刷新缓冲区就是把缓冲区里面的内容在屏幕上显示出来 )
情况1 : return /exit /程序结束
情况2 : 缓冲区满了 ,就会自动把缓冲区里面的内容刷新到液晶屏上显示出来
情况3 : 回车
1 修改链表的数据域 ,向里面增加一个标记位 ,遍历链表 ,每次遍历到未遍历过的节点 ,则将节点的标记位修改 ,若出现了标记位是修改过的值的节点 ,则证明链表存在循环 。
2 若链表是只读的 ,不能修改数据域 ,则建立一个数组 ,访问每一个元素 ,并将元素存入链表中 ,然后检查下一个节点 ,拿它的数据和数组中的数据进行遍历比较 ,若出现在数组中 ,则存在循环 。
3 内存容量有限 ,不能使用数组 ,假定环出现在前N个节点处 ,则建立一个指针 ,指向头结点的下一个结点 ,随后遍历链表和建立的指针比较 ,若无相同 ,则建立的指针指向下一个结点 ,继续遍历链表与其比较 ,直到出现有相同的节点 ,则证明有循环 ;若新建指针已经到达第N个节点 ,则无循环 。
4 内存容量有限 ,不能使用数组 ,并且循环可能出现在链表任何一处 ,则我们先排除链表只有三个指针的情况 ,随后建立两个指针P1 、P2 ,P1指向头结点的下一个节点 (第二个节点 ),P2指向头结点下一个节点的下一个节点 (第三个节点 ),然后我们每一次令P1向后指向一个节点 ,P2每次向后指向两个节点 ,进行循环 ,若最终P1 、P2都指向NULL ,则没有循环 ,若最终出现P1和P2指向同一个节点 ,则证明存在循环 。
动态库 :后缀名是 .so结尾
静态库 :后缀名是 .a结尾
动态库特点 /问题 :
1 使用动态库编译程序 ,动态库的源码不会被编译到程序中 ,需要等到程序运行的时候才去库文件中加载源码
2 只要你使用动态库去编译程序 ,运行程序的时候 ,都会去系统的环境变量中去寻找动态库文件 ,找不到就报错
静态库特点 /问题 :
1 你使用静态库编译 ,静态库中的源码会被一起编译到程序中 ,运行程序的时候不需要依赖静态库
2 静态库编译的程序占用的存储空间要比动态库编译的大
对比动态库和静态库 :
结论1 :动态库编译的程序占用的存储空间要比静态库编译的小
原因 :动态库编译程序 ,动态库里面的源码没有编译到程序里面的
静态库编译程序 ,静态库里面的源码需要编译到程序里面的
结论2 :如果动态库和静态库放在一起 ,编译器优先使用谁 ??
优先使用动态库编译
第一步 :预处理 (处理C程序中所有 #开头的语句 )
把 #开头的语句展开 ,gcc hello .c - o hello .i - E
(- E 编译选项 ,用来对程序进行预处理 )
第二步 :编译 (把 .i文件编译得到 .s的汇编文件 )
gcc hello .i - o hello .s - S
第三步 :汇编 (.o文件叫做可重定位文件 )
gcc hello .s - o hello .o - c
第四步 :链接生成最终的可执行程序
gcc hello .o - o hello
什么叫模块化,设计一个系统时是否分的模块越多越好(格力笔试)
模块化就是程序划分成可独立命名且独立访问的模块 ,每个模块完成一个子功能 ,把这些模块集成起来构成一个整体 ,可以完成指定的功能满足用户的需求 。
不一定越多越好 ,当模块数目增加时 ,每个模块的规模将减小 ,开发单个模块需要的成本确实减少了 ,但是 ,随着模块数量增加 ,设计模块之间连接所需要的工作量也将增加 ,所以设计一个系统时并不一定模块越多越好 。
首先SPI其实是串行外设接口的缩写 ,是一种全双工 、同步的通信总线 ,并且只占用了芯片的四根线 ,节约了芯片的管脚 ,具有简单易用的特性 ,是一种高速 、高效率的串行接口技术 。
SPI四条逻辑线 : 1 MISO 主机输入 ,从机输出 (数据来自从机 )
2 MOSI 主机输出 ,从机输入 (数据来自主机 )
3 SCLK 串行时钟信号 ,由主机发送给从机
4 NSS 片选信号 ,由主机发送 ,以控制与哪个从机通信 , 通常是低电平有效
SPI的优点 :
1 全双工串行通信 ;
2 高速数据传输速率 。
3 简单的软件配置 ;
4 极其灵活的数据传输 ,不限于8位 ,它可以是任意大小的字 ;
5 非常简单的硬件结构 。从站不需要唯一地址 (与I2C不同 )。从机使用主机时钟 ,不需要精密时钟振荡器 /晶振 (与UART不同 )。不需要收发器 (与CAN不同 )。
SPI的缺点 :
1 没有硬件从机应答信号 (主机可能在不知情的情况下无处发送 );
2 通常仅支持一个主设备 ;
3 需要更多的引脚 (与I2C不同 );
4 没有定义硬件级别的错误检查协议 ;
5 与RS - 232 和CAN总线相比 ,只能支持非常短的距离 (1 到3米 );
SPI使用时的通信模式 :
一 :多NSS (和对应的从机通信时 ,响应的NSS为低电平 ,其他的NSS为高电平 ,若出现多个从机的NSS同时为低电平 ,则可能他们都试图从一条MISO线上传输数据 ,最后导致接收的数据为乱码 )
二 :菊花链 (数据信号以串行的方式从一个设备依次传入下一个设备 ,不断循环直到数据到达目标设备 ,缺点是若某一设备出现问题 ,那么比它优先级低的设备就接收不到数据了 ,离主机距离越远 ,优先级越低 )
SPI总线有四种模式 ,分别是由时钟极性CKP和时钟相位CKE配合而来 :
1 时钟极性CKP = 0 ;时钟相位CKE = 0
2 时钟极性CKP = 0 ;时钟相位CKE = 1
3 时钟极性CKP = 1 ;时钟相位CKE = 0
4 时钟极性CKP = 1 ;时钟相位CKE = 1
(时钟极性 :CKP = 0 空闲状态为低电平 ;CKP = 1 空闲状态为高电平 )
(时钟相位 :CKE = 0 从第一个时钟边沿开始传输数据 ;CKE = 1 ,从第二个时钟边沿开始传输数据 )
RS-232(SCI串行通信接口的一种)(都是串口通信)(CVTE一面延伸)
定义 :RS -232 是常用的串口通信接口标准之一 。(全双工通讯方式 )
RS - 232 接口一般是9针的 ,其中分别为 :RXD ,TXD ,GND ,DCD ,DTR ,DSR ,RTS ,CTS ,RI 。
各针位功能 :
DCD - 载波检测通知给DTE ,RXD - 接收数据 ,TXD - 发送数据 ,GND - 0 电平位 ,DTR - DTE告诉DCE准备就绪 ,DSR - DCE告诉DTE准备就绪 ,RTS - 请求发送 (DTE向DCE发送数据请求 ),CTS - 清除发送 (DCE通知DTE可以传数据 ),RI - 振铃指示 (DCE通知DTE有振铃信号 )
其中DTR ,DSR ,RTS ,CTS用于硬件流控 ;硬件连接的方式分为9线连接 、5 线连接和3线连接 ,一般只使用3线连接 (RXD ,TXD ,GND ),无法实现硬件流控功能 ,即大量的数据传输 ,若要实现大量数据传输 ,要使用5线或者9线连接 。
特点 (优点 ):
1 RS - 232 的电气逻辑标准 (TTL )是负逻辑 ,+3 V到 + 15 V为逻辑 "0 ",-3 V到 -15 V为逻辑 "1 ",-3V到+3V为非法状态。
2 信号线少 ,RS - 232 规定了25条信号线 ,而在一般应用中 ,使用3条到9条信号线就可实现全双工通信 ,使用RXD ,TXD ,GND三线即可实现简单的全双工通信 。
3 灵活的波特率选择 ,可以根据不同设备的传送速率来选择对应的波特率 ,对于慢速外设可以选择较低的传送速率 ,反之也可 。
4 理论上传送距离较远 ,RS - 232 采用普通的串行传送方式的话 ,传送距离一般为30米 ,还可搭配其他外设达到更远的传送距离 ,例如光纤等 。
缺点 :
1 接口的信号电平较高 ,容易损坏接口电路的芯片 ,又因为它是负逻辑 ,想要与常规TTL电路连接的话需要使用电平转换电路 。
2 传输速率较低 ,异步传输时波特率为20Kbps ,在CPLD开发板中 ,综合程序波特率只能选19200 。
3 接口使用一根信号线和一根信号返回线 (RXD和TXD )构成共地的传输方式 ,这种共地传输容易产生共模干扰 ,抗噪声干扰性弱 。
4 传输距离有限 ,实际使用时只能在15米左右 。
三种通讯方式 :
1 两设备直接通过RS - 232 标准通讯 :发出TTL电平通过电平转换芯片转换成232所用的电平 。
2 USB转串口和电脑进行通讯 :使用CH340电平转换芯片 ,并且电脑安装CH340驱动 。
3 直接通过TTL电平进行通讯 ,一般用于与带串口的设备或者传感器通讯 ,不需要通过电平转换芯片 ,例如和GPS模块 、串口转WIFI模块 、HC04蓝牙模块等通讯 。
工业通信中一般使用RS - 485 ,因为RS - 485 是差分信号 ,可以抑制共模干扰 ,因此在恶劣的环境中拥有很好的抗干扰性 ,比较稳定
UART(SCI和UART就是的士和出租车的区别)(CVTE一面延伸)
定义 :通用异步收发传输器
发送端的UART将来自控制设备 (例如CPU )的并行数据转换为串行数据 ,以串行方式将其发送到接收端的UART ,然后由接收端的UART将串行数据转换成并行数据以用于接收设备的正常处理 。
UART的数据传输只需要两条线 ,分别是RX和TX ,TX为发送端 ,RX为接收端 。
TTL :
输入高电平最小为2V ,输出高电平最小为2 .4 V ,典型值是3 .4 V 。
输入低电平最大为0 .8 V ,输出低电平最大为0 .4 V ,典型值为0 .2 V 。
优点 :
1 通信只需要两条数据线 ;
2 无需时钟信号 ;
3 有奇偶校验位 ,方便通信的差错检查 ;
4 只需要接收端和发送端设置好数据包结构 ,即可稳定通信 。
缺点 :
1 数据帧最大只能支持9位的数据 ;
2 不支持多主机或者多从机的主从系统 。
现今通常使用外部USB转UART转换器 ,例如CH340驱动 ,现在很多处理器和芯片都内置了UART 。
通讯方式 :异步通讯 ,即半双工 (无法同时接收与发送 ,同一时刻接收与发送只能实现一个 )
组成 :由CAN_HIGH和CAN_LOW两条信号线组成 。(没有时钟信号线 )
又由这两条信号线可以组成两种网络 :
1 闭环总线网络 ,特点 :高速 、短距离 、闭环 ,最高速度为1Mbps ,最长距离40m 。
2 开环总线网络 ,特点 :传输距离远 、开环 ,最高速度为125Kbps ,最远距离1km 。
通讯节点 :CAN协议可以挂载多个节点 ,通过总线来实现节点通讯 ,可以对节点的数据内容进行编码 ,理论上节点个数不受限制 。
通讯节点构成 :由一个CAN控制器和一个CAN收发器组成 。
通讯节点收发信号 :
1 发送数据 :控制器发送一个信号 (0 或1 ),收发器将这个信号变成差分信号传送到总线中 。
2 接收数据 :收发器将差分信号转化为0或1的二进制编码 。
波特率要求 :由于CAN协议没有时钟信号线 ,各个节点要用相同特定的波特率进行通讯 。
Linux内核中提供了API给程序员使用CAN协议 。
STM32上也有对应的外设 ,提供了相应的库函数给程序员使用 。
在CAN总线接口协议中 ,错误状态可分为三种 ,主动错误 ,被动错误和总线关闭 。
1 主动错误状态是可以正常参加总线通信的状态
2 处于被动错误状态是可以正常参加总线通信的状态 ,处于被动错误状态的单元即使检测出错误 ,而其他处于主动错误状态的单元如果没发现错误 ,整个总线也被认为是没有错误的 。
3 总线关闭态是不可以参加总线上通信的状态 ,信息的接收和发送均被禁止 。
IIC通信由两根线构成 ,分别是 : 1 信号线 (SDA )
2 时钟线 (SCL )
优点 /作用 :
1 简化了硬件电路PCB的布线 ,降低了系统成本 ,提高了系统可靠性 。(为什么 ?因为IIC总线只需要两根线 ,总线接口集成在了芯片内部 ,不需要特殊的接口电路 ,芯片内除了这两根线和少量的中断线 ,没有其他的线 ,用户IC可以很容易形成标准化和规模化 ,便于重复利用 。)
2 数据传输和地址设定由软件来决定与设定 ,非常灵活 。总线上器件的增加和删除不影响其他器件的正常工作 。
3 IIC总线可通过外部连线进行在线检测 ,便于系统故障诊断和调试 ,故障可立即被寻址 。
4 连接到相同总线上的IC数量只受总线最大电容的限制 。
5 总线具有极低的电流消耗 ,抗高噪声干扰 。
缺点 :
1 传输速率慢 。
2 IIC是半双工通信 ,在同一时间只能进行接收数据或者发送数据 。
IIC协议层 :
开始信号 :SCL为高电平时 ,SDA由高电平向低电平跳变 ,开始传送数据 。
结束信号 :SCL为高电平时 ,SDA由低电平向高电平跳变 ,结束传送数据 。
应答信号 :接收数据的IC在接受到8bit数据后 ,向发送数据的IC发出特定的低电平脉冲 ,表示已收到数据 。CPU向受控单元发出一个信号后 ,等待受控单元发出一个应答信号 ,CPU接到应答信号后 ,根据实际情况作出是否继续传递信号的判断 。若未收到应答信号 ,则判断为受控单元出现故障 。
目前常用的USB版本 :USB2 .0 和USB3 .0
USB 2.0 传输速率480Mbps (60 MB /s )
USB 3.0 传输速率 5 Gbps (500 MB /s )
USB2 .0 使用4芯的屏蔽线 :一对差分线 (D + ,D - )传输信号 ,另一对 (VCC和GND )传输5V的直流电 。
USB3 .0 设计了9条内部线路 ,其中继承了USB2 .0 的D + ,D - ,VCC和GND ,还有一对为USB3 .0 专门设计的线路SSRX和SSTX ,分别为 :SSRX + ,SSRX - ,SSTX + ,SSTX - 以及一条GND_GRAIT线 。
USB设备分为三层 :
1 最底层 :总线接口 ,用于发送和接收数据包 。
2 中间层 :处理总线接口和不同的端点之间的数据流 。
3 最上层 :USB设备提供的功能 。
USB的传输方式 :
1 批量传输 :用在需要大量传输数据 ,但对实时性要求不高的情况下 。(U盘 )
2 中断传输 :中断传输的数据量很小 ,一般用于通知Host某个事件的来临 。(USB鼠标 ,键盘等 )这里的中断不是硬件上的中断 ,而是USB主机按照指定的时间不断查询设备是否有数据传输 。
3 同步传输 :要保证信息传输的同步性 (摄像头 )。特点 :虽然要求实时性 ,但不要求百分百的正确 。
4 控制传输 :负责向USB设置一些控制信息 。一个USB控制器下面挂接很多设备 ,要怎么传输数据 ,还有寻址等 ,都是通过控制传输建立起来的 。
USB主机和从机之间的通信通过管道来实现 ,任何一个USB设备上电后就会存在一个管道 ,USB主机通过管道来获取从机的描述符 、配置等信息 。在主极端 ,管道骑士就是一组缓冲区 ,用来存放主机数据 ,在设备端管道对应一个特定的端点 。
端点 :
作用 :主机通过端点与设备进行通讯 ,以使用设备的功能 。
定义 :端点实际上就是一个一定大小的数据缓冲区 。
特点 :
1 每个端点都有一定的特性 ,包括传输方式 、总线访问频率 、带宽端点号 、数据包最大容量等 。
2 端点必须在设备配置后才能生效 (端点0除外 ,它是用来初始化设备的 )。
管道 :
定义 :一个USB管道是驱动程序的一个数据缓冲区与一个外设端点的连接 。代表了两者之间传输数据的能力 (只是一个逻辑上的概念 ,不是真实存在的 )。
分类 : 1 数据流管道 (其中的数据没有USB定义的结构 );
2 消息管道 (其中的数据必须有USB定义的结构 );
特点 : 1 所有设备必须支持端点0作为设备的控制管道 。
2 通过控制管道 ,可以获取完全描述USB设备的信息 。
作用 :防止程序无限制的执行 ,造成死循环 。可以用在接收和发送数据时对接收和发送超时进行处理 ,起到数据保护和保护电路的作用 。
原理 :本质上是一个定时器电路 。系统正常运行时 ,需要在一定时间间隔内对看门狗计数器清零 ,即喂狗 ,不让复位信号产生 ;一旦出现问题 ,没有执行 “喂狗 ”,系统将会被复位 。
种类 : 1 CPU内部自带的看门狗
2 独立的看门狗芯片
使用方法 :在程序中隔一段时间执行一次喂狗操作 ,即在一个完整的程序段中 ,间隔性放入多个喂狗操作 。(放入主程序中或者放在中断子程序中 )
什么时候会失效 :
1 系统内部定时器自身发生故障 ,看门狗就会失效
2 中断系统故障导致定时器中断失效
3 整个程序死机 ,主程序出现异常
RAM :随机存取存储器 ,是与CPU直接交换数据的内部存储器 ,用于存放各种现场的输入 /输出数据 ,或者当作堆栈使用 。(例如 :运行内存 )
ROM :只读存储器 ,事先写好的 ,整机工作过程中只能读出 ,不能加以改写 。(例如 :存储系统相关的信息和BIOS等 )
区别 :RAM在断电后保存在上面的数据会消失 ,而在ROM上的数据不会自动消失 ,可以长时间断电保存 。
51单片机中,可拓展的外部程序储存器最大为多少字节
分两种情况 :(16 位寻址 ,故最多为2 ^16 = 64 KB )
1 EA接高电平 ,单片机先从内部读取程序 ,超出部分从外部读取 ,故外部可拓展60K字节 。(内部4K ,外部60K )
2 EA接低电平 ,单片机直接从外部读取程序 ,故可拓展64K字节 。
1 子程序调用和中断服务时CPU自动将当前PC值压栈保存 ,返回时自动将PC值弹栈 。
2 保护现场 /恢复现场 。
3 数据传输 。
1 准双向口配置
2 开漏输出配置
3 推挽输出配置
4 高阻配置
1 实时处理功能 :实时控制中 ,现场各种外界变量变化 ,这些变量可根据要求随时向CPU发出中断申请 ,请求CPU及时处理中断请求 。
2 故障处理功能 :针对难以预料的故障 ,如掉电等 ,可通过中断系统由故障源向CPU发出中断请求 ,再由CPU转到对应的故障处理程序进行处理 。
3 分时操作 :使CPU与外设同时工作 。CPU在启动外设工作后 ,继续执行主程序 ,同时外设也在工作 ,每当外设做完一件事就发出中断申请 ,请求CPU中断其正在执行的程序 ,转去执行中断服务程序 ,中断处理完后 ,CPU恢复执行主程序 ,外设继续工作 。这样CPU可启动多个外设同时工作 ,大大提高效率 。
定义 :串口一旦接收到数据就会发生中断 ,同时收到数据就会清除标识位 (这是串口没有清除标识位也能正常工作的原因 )
边沿触发 :包括上升沿触发和下降沿触发 ,边沿触发检测的是电平的变化 ,高电平转化成低电平或者低电平转化成高电平时会触发
电平触发 :分为高电平触发和低电平触发 。
边沿沿触发是锁存中断信号的 ,由D触发器记忆 ,即 :若CPU来不及响应中断 ,外部中断信号撤消后 ,由于D触发器的记忆作用 ,消失的中断信号仍然有效 ,直到中断被响应并进入中断ISR ,记忆的中断信号才会由硬件自动清除 。
电平触发根据硬件设计的不同 ,分为即时触发和信号锁存触发 ;
1 即时的电平触发 ,当外部中断信号撤消时 ,中断申请信号随之消失 。如果在外部中断信号申请期间 ,CPU来不及响应此中断 ,那么有可能这次中断申请就漏掉了 。即时的电平触发是一个时间段 ,需要一直触发中断的 ,就用电平触发 。比如高电平触发 ,只要检测到是高电平就触发中断 。
2 信号锁存的电平触发 ,当检测到高电平或低电平信号 ,该触发信号也会被锁存 ,类似于边沿触发 ,但是触发信号需要进行手动清除
边沿触发及电平触发的区别
如果是采用边沿检测外部中断 ,检测到电平变化会中断 ,但是如果中断检测口一直保持某一电平 ,则无法产生下次中断 ,需要等下次检测到电平变化才会中断 。中断标志在得到响应后由硬件自动清除 。
如果是采用电平检测外部中断 ,检测到低 /高电平会中断 ,但是如果中断检测口一直保持低电平 ,中断处理完成后 ,会继续产生下次中断 ,需要检测到高电平才会停止中断产生 。中断标志在得到响应后由硬件手动清除 。
1 电源部分 (电源 )
2 晶振部分 (晶振 )
3 复位部分 (复位电路 )
数字电路中只存在两种电平 ,高电平和低电平 ,在刚刚通电的时候 ,电路中的这些状态是不确定的 ,为了使电路确定状态 ,必须使用上拉电阻和下拉电阻 ;使一个不确定的电平变成高电平的是上拉电阻 ,使一个不确定的电平变成低电平的是下拉电阻 。
IO口最主要的功能用来与外部器件实现数据信息的交互 、速度匹配 、数据传送方式和增强单片机的负载能力 。
七种
1 立即寻址
2 直接寻址
3 寄存器寻址
4 寄存器间接寻址
5 相对寻址
6 变址寻址
7 位寻址
定义 :单片机的PC是程序计数器 ,一般用于存放将要执行的指令地址 ,是一个16位的寄存器 。当执行一条指令时 ,首先需要根据PC中存放的指令地址 ,将指令取出送到指令寄存器中 ,这个过程称为 “取指令 ”。于此同时 ,PC中的地址自动加1跳转操作 ,得到下一条指令的地址 。当前一条指令执行完毕 ,CPU再根据PC取出下一条指令的地址 ,并再得到下一条指令地址 ,一次执行每一条指令 。
定义 :在片内RAM中 ,指定一个专门的区域来存放某些数据 ,它遵循后进先出的原则 ,这个RAM区叫堆栈 。(通常是RAM区靠后的位置 ,因为一般不安排在工作寄存器和可按位寻址的RAM区 )
作用 :
1 子程序调用和中断服务时CPU自动将当前PC值压栈保存 ,返回自动将PC值弹栈 。
2 保护现场 /恢复现场
3 数据传输
DMA :直接存储器访问
DMA传输数据从一个地址空间复制到另一个地址空间 ,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输 (必须是高速 ,不能是不同速度 )。两种外设通过DMA快速传递数据 ,不需要通过CPU ,节省了CPU的资源来做其他操作 。
所需的核心参数 :
1 数据的源地址 (主要 )
2 数据传输的目标地址 (主要 )
3 传递数据多少的数据传输量 (主要 )
4 进行多少次传输的传输模式
工作步骤及原理 :
在实现DMA传输时 ,由DMA控制器掌管总线 ,因此存在一个总线控制权转移问题 。即DMA传输前 ,CPU要把总线的控制权交给DMA控制器 ,而在DMA传输后 ,DMA控制器应立即把总线控制权再交回给CPU 。一个完整的DMA传输过程必须经过DMA请求 、DMA响应 、DMA传输 、DMA结束4个步骤 。
DMA和CPU有三种方法分时使用内存 :
1 停止CPU访问内存 (在DMA传送过程中 ,CPU基本处于不工作状态或者说保持状态 )
2 周期挪用 (当IO设备没有DMA请求时 ,CPU按程序要求访问内存 ,一旦有DMA请求 ,则由IO设备挪用一个或几个内存周期 )
3 DMA和CPU交替访问内存 (当CPU工作周期比内存存取周期长很多 ,此时采用交替访内的方法使DMA传送和CPU同时发挥最高的效率 )