C语言
C语言编译过程,各过程作用
-
预处理阶段
将源码文件预处理:
gcc -E xx.c -o xx.i
-
编译阶段
生成汇编代码,检查语法错误:
gcc -S xx.i -o xx.s
gcc -S xx.c -o xx.s
-
汇编阶段
生成目标代码:
gcc -C xx.s -o xx.o
gcc -C xx.c -o xx.o
-
链接阶段
将目标文件链接生成可执行程序:
gcc xx.o -o xxx
运行 : ./xxx
- 编译程序负责把
.c
文件被编译为.obj
目标文件,链接程序负责把一个或多个.obj
目标文件被链接成一个.exe
文件。
- 编译程序负责把
static、volatile、const、inline、extern作用
-
static作用(限定作用域,将空间定义在静态区)
- 由static修饰的变量只会被初始化一次
- 由static修饰的全局变量,改变其变量的作用域为本文件.c中
- 由static修饰的局部变量,延长其生命周期:从定义的开始到程序结束(将局部变量定义在静态区域,这样函数下次可以访问同一个空间)
- 由static修饰的函数只能被本文件.c所调用
-
extern作用(引用其它文件【外部】定义的全局变量)
extern标识的变量函数声明定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找定义
-
const作用
const用来定义一个只读的变量或者对象,分成2方面:
1.修饰指针 (修饰的指针保存的地址不可变性)
2.修饰数据 (修饰的数据具有不可变性)
(const在号左侧修饰指针指向的变量,const在右侧修饰的是指针)
-
int const *p 指针常量
-
const int *p 指针常量,和上面的一致
-
int *const p 常量指针
-
const int * const p 常量指针常量
-
-
volatile作用(no cache)
用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不用进行编译优化,以免出错
a) 防止编译器对代码进行优化 每次读写变量,结果都是直接更新到 实际的地址中
主要用在多个执行单元同时访问一个变量/寄存器的情况.
(valotile的使用主要是在以下三个方面)
-
中断和进程共享变量
-
进程和进程间通信
-
寄存器,程序读写寄存器结果直接写入寄存器,防止中间 缓存的问题.
-
-
static inline:
类似宏函数,该参数修饰的函数,在被调用的地方被自动展开,减少函数调用的层次,节省CPU资源,但是会导致程序变大!
堆区和栈区的区别
- 存储内容:栈存储局部非静态变量、函数参数等,堆存储使用new、malloc申请的变量等;
- 申请方式:栈内存由系统分配,堆内存由自己申请;
- 申请后系统的响应:栈----只要占的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出;堆----首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序;
- 申请大小的限制:Linux下栈的大小一般是10M,堆的容量较大;
- 申请效率的比较:栈由系统自动分配,速度较快,堆使用new、malloc等分配,较慢;
- 栈的优势在于处理效率,堆区的优势在于灵活。
- 一、栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈;
2、堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
二、堆栈缓存方式区别:
1、栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放;
2、堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
三、堆栈数据结构区别:
堆(数据结构):堆可以被看成是一棵树,如:堆排序,FIFO队列
栈(数据结构):一种先进后出的数据结构 FILO
大端小端【字节序】
不同CPU使用的字节序不一致,导致终端通信的时候数据混乱
大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。
小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。
/一般操作系统都是小端,而通信协议是大端的/
如何去判断机器是大端模式还是小端模式?
第一种:
#include <stdio.h>
int main(void)
{
int i = 0x12345678;
char c = i;
printf("%x\n",c);
return 0;
}
如果它打印出78(大端模式),如果打印出12(小端模式)。
第二种:
#include <stdio.h>
typedef union NODE
{
int i;
char c;
}Node;
int main(void)
{
Node node;
node.i = 0x12345678;
printf("%x\n",node.c);
return 0;
}
- 注:这种方式用到了union(共用体),所谓的共用体,就是共同使用一块内存,共用体的大小是共用体中的所有类型最大的哪一个。
结构体对齐【自然对齐】
为了提高计算机效率,结构体在内存中存取是按字长对齐的。
自然对齐:一个基本类型变量大小和他所在的地址成倍数关系,则成为该变量是自然对齐的.比如 int a,所在的地址应该是 0 4 8 12 16 20……….
规则一:结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存放在offset为该数据成员大小的整数倍的地方(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
规则二:如果一个结构体B里嵌套另一个结构体A,则结构体A应从offset为A内部最大成员的整数倍的地方开始存储。(struct B里存有struct A,A里有char,int,double等成员,那A应该从8的整数倍开始存储。),结构体A中的成员的对齐规则仍满足原则1、原则2。
规则三:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
- tips:
- 结构体A所占的大小为该结构体成员内部最大元素的整数倍,不足补齐。
- 不是直接将结构体A的成员直接移动到结构体B中
案例解析:
typedef struct A{
int a;
short b;
}A;
/**
* 内存对齐规则:
* 按最长的类型的长度为最长长度
* 4*0 = 0 所以从 0 位置开始放
* 2*0 = 0 0位置已经有内容
* 2*1 = 2 2位置已经有内容
* 2*2 = 4 4位置空,可以存放
* short长度 为2 而基准长度为4,因此 需要将剩余的两字节补空
* 故 A大小为8字节
* a a a a
* b b - -
*/
printf("%d\n",sizeof(A));//8
typedef struct B{
double a;
short b;
int c;
}B;
/**
* 最长基准长度为8
* 8*0 = 0 放于0位置起的8个字节
* 2*0 = 0 此处有内容
* ...
* 2*4 = 8 此处空闲可以存放 8-10用于存放 short
* 4*0 = 0 此处已经有内容
* ...
* 4*3 = 12 此处可以存放 12-15
* a a a a a a a a
* b b - - c c c c
* 所以B共占用 16字节
*/
printf("%d\n",sizeof(B));
typedef struct C{
short a;
double b;
int c;
}C;
/**
* 最长基准长度为:8
* 2*0 = 0 0-1位置用于存放a
* 8*0 = 0 此处已有内容
* 8*1 = 8 8-16位置用于存放b
* 4*0 = 0 此处已有内容
* 4*3 = 12 12-16存放c
* a a - - - - - -
* b b b b b b b b
* c c c c - - - -
*/
printf("%d\n",sizeof(C)); //24
typedef struct D{
int a;
double b;
}D;
typedef struct E{
short a;
int b;
D c;
}E;
/**
* 当结构体内部嵌套结构体时,以两个结构体内部最长的类型为基准长度
* 此处结构体D与E最长的长度为D中的double,因此基准长度为:8
* 2 * 0 = 0 0-1位置存放E.a
* 4 * 0 = 0
* 4 * 1 = 4 4-7位置存放E.b
* 8 + 4 * 0 = 8 8-11位置存放D.a
* 8 + 8 * 0 = 8
* 8 + 8 * 1 = 16 16-23位置存放D.b
*/
printf("%d\n",sizeof(E)); //24
全局变量和局部变量的区别
-
作用域不同
-
存储方式不同:全局变量在全局数据区中,局部变量在栈区
-
生命周期不同:全局变量随程序结束而销毁,局部变量在其作用区结束时销毁
-
使用方式不同:全局变量载程序的各个部分都可以使用,局部变量只能在局部使用(函 数内部会优先使用局部变量再使用全局变量)
宏函数注意事项【括号 副作用【括号 ++问题】
各种段理解【堆 栈 代码段 数据段(只读 未初始化段)】
- 栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其
操作方式类似于数据结构中的栈.
- 堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回
收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
- 全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的
全局变量和静态变量在一块区域(.data), 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(.bss)。 - 程序结束后由系统释放。
-
文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
-
程序代码区—存放函数体的二进制代码。
函数指针和指针函数
指针函数:
定义:指针函数,简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。
声明格式:类型标识符 \*函数名(参数表)
函数声明:
int func(int x,int y);
一个函数,然后返回值是一个 int 类型,是一个数值。
接着看下面这个函数声明:
int *func(int x,int y);
这和上面那个函数唯一的区别就是在函数名前面多了一个*号,而这个函数就是一个指针函数。其返回值是一个 int 类型的指针,是一个地址。
这样描述应该很容易理解了,所谓的指针函数也没什么特别的,和普通函数对比不过就是其返回了一个指针(即地址值)而已。
示例:
typedef struct _Data{
int a;
int b;
}Data;
//指针函数
Data* f(int a,int b){
Data * data = new Data;
data->a = a;
data->b = b;
return data;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//调用指针函数
Data * myData = f(4,5);
qDebug() << "f(4,5) = " << myData->a << myData->b;
return a.exec();
}
输出如下:
f(4,5) = 4 5
- 注意:在调用指针函数时,需要一个同类型的指针来接收其函数的返回值。
不过也可以将其返回值定义为 void*类型,在调用的时候强制转换返回值为自己想要的类型,如下:
//指针函数
void* f(int a,int b){
Data * data = new Data;
data->a = a;
data->b = b;
return data;
}
调用:
Data * myData = static_cast<Data*>(f(4,5));
其输出结果是一样的,不过不建议这么使用,因为强制转换可能会带来风险。
函数指针:
定义:函数指针,其本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针变量。
通常我们所说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。
函数指针可以像一般函数一样,用于调用函数、传递参数。
函数指针变量的声明:
声明格式:类型说明符 (*函数名) (参数)
typedef int (*fun_ptr)(int,int);//声明一个指向同样参数、返回值的函数指针类型
函数指针是需要把一个函数的地址赋值给它,有两种写法:
fun = &Function;
fun = Function;
取地址运算符&不是必需的,因为一个函数标识符就表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。
调用函数指针的方式也有两种:
x = (*fun)();
x = fun();
两种方式均可,其中第二种看上去和普通的函数调用没啥区别,如果可以的话,建议使用第一种,因为可以清楚的指明这是通过指针的方式来调用函数。
示例:
int add(int x,int y){
return x+y;
}
int sub(int x,int y){
return x-y;
}
//函数指针
int (*fun)(int x,int y);
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//第一种写法
fun = add;
qDebug() << "(*fun)(1,2) = " << (*fun)(1,2) ;
//第二种写法
fun = ⊂
qDebug() << "(*fun)(5,3) = " << (*fun)(5,3) << fun(5,3);
return a.exec();
}
输出如下:
(*fun)(1,2) = 3
(*fun)(5,2) = 2 2
实例:
以下实例声明了函数指针变量p,指向函数max:
#include <stdio.h>
int max(int x, int y)
{
return x > y ? x : y;
}
int main(void)
{
/* p 是函数指针 */
int (* p)(int, int) = & max; // &可以省略
int a, b, c, d;
printf("请输入三个数字:");
scanf("%d %d %d", & a, & b, & c);
/* 与直接调用函数等价,d = max(max(a, b), c) */
d = p(p(a, b), c);
printf("最大的数字是: %d\n", d);
return 0;
}
编译结果:
请输入三个数字:1 2 3
最大数字是: 3
二者区别:
定义不同:
指针函数本质是一个函数,其返回值为指针。
函数指针本质是一个指针,其指向一个函数。
写法不同:
指针函数:int* fun(int x,int y);
函数指针:int (fun)(int x,int y);
可以简单粗暴的理解为,指针函数的是属于数据类型的,而函数指针的星号是属于函数名的。
再简单一点,可以这样辨别两者:函数名带括号的就是函数指针,否则就是指针函数。
回调函数以及作用
函数指针作为某个函数的参数
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。
简单讲:回调函数是由别人的函数执行时调用你实现你实现的函数。
以下是来自知乎作者常溪玲的解说:
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过来几天店里到货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。
在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的时间,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。
实例:
#include <stdlib.h>
#include <stdio.h>
// 回调函数
//populate_arry函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
for (size_t i=0; i<arraySize; i++)
array[i] = getNextValue();
}
// 获取随机值
//定义了回调函数getNextRandomValue,它返回了一个随机值,它作为一个函数指针传递给populate_array函数。
//populate_array将调用10次回调函数,并将回调函数的返回值赋值给数组。
int getNextRandomValue(void)
{
return rand();
}
int main(void)
{
int myarray[10];
/* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/
populate_array(myarray, 10, getNextRandomValue);
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
}
printf("\n");
return 0;
}
编译执行结果如下:
16807 282475249 1622650073 984943658 1144108930 470211272 101027544 1457850878 1458777923 2007237709
如何判断浮点数相等
对于两个浮点数比较只能通过相减并与预先设定的精度比较,记得要取绝对值。
if (fabs(f1 - f2) < 预先指定的精度)
{
...
}
示例:
#define EPSILON 0.000001 //根据精度需要
if (fabs(fa - fb) < EPSILON)
{
printf("fa<fb\n");
}
fabs函数(求浮点数绝对值)与abs函数(求整数绝对值)
数学函数:fabs()
原型:extern float fabs(float x);
用法:#include <math.h>
功能:求浮点数x的绝对值
说明:计算|x|, 当x不为负时返回x,否则返回-x
数据结构
栈和队列的区别
顺序栈、链式栈;顺序队列、链式队列
栈(static)和队列(queue)是两种操作受限的线性表。这种受限表现在:
- 队列:先进先出(First In First Out) FIFO,队列只允许在表尾插入数据元素,在表头删除数据元素。
- 栈: 后进先出(First In Last Out) FILO,栈的插入和删除操作只允许在表的尾端进行(在占中称为“栈顶”)
相同点:
- 都是线性结构
- 插入操作都是限定在表尾进行
- 都可以通过顺序结构和链式结构实现
- 插入和删除的时间复杂度是O(1),在空间复杂度上两者也一样
不同:
- 删除数据元素的位置不同,栈的删除操作在表尾进行,队列的删除操作在表头进行。
- 应用场景不同;常见栈的应用场景包括括号问题的求解、表达式的展缓和求值、函数调用和递归实现深度优先搜索遍历等;常见的队列的应用场景包括计算机系统中各种资源的管理,消息缓冲器的管理和广度优先搜索遍历等。
- 顺序栈能够实现多栈空间共享,而顺序队列不能。
环形队列
队列,先进先出的结果. 环形队列,首尾相连的队列,一般用作缓存[R W]。
环形队列实现原理
内存上没有环形的结构,因此环形队列是通过数组的线性空间、循环队列来实现的。当数据到了尾部如何处理呢?他将转回到0位置来处理。这个转回是通过取模操作来执行的。
因此环形队列是逻辑上将数组元素q[0]与q[MAX - 1]链接,形成一个存放队列的环形空间。
链表和数组区别(内存空间连续与否)
数组占用连续的空间,一次分配不可改动,随机访问, 增删改查细节
链表不占用连续的空间, 动态分配空间, 需要遍历访问 增删改查细节
不同:链表是链式的存储结构;数组是顺序的存储结构。
链表通过指针来连接元素与元素,数组则是把所有元素按次序存储。
链表的插入删除元素相对数组较为简单,不需要移动元素,且较为容易实现长度扩充,但是寻找某个元素较为困难。
数组寻找某个元素较为简单,但是插入与删除比较复杂,由于最大长度需要在编程一开始时指定,故当达到最大长度是,扩充长度不如链表方便。
相同:两种数据结构均可实现数据的顺序存储,构造出来的模型呈线性结构。
-
数组便于查询和修改,但是不方便新增和删除
-
链表适合新增和删除,但是不适合查询,根据业务情况使用合适的数据结构和算法是在大数据量和高并发时必须要考虑的问题
单链表如何逆序【最简单高效方式】
分离表头和元素,将后面的元素依次插入表头后面即可. 参考笔记:代码
双向链表
能访问上一结点的链表,相对于单向链表只记录后一个元素的位置,双向链表也会记录上一个节点的位置。
排序方法
冒泡 快速 选择
查找方法
顺序 二分 hash查找 目录查找
笔试中,一般要求 冒泡/选择 排序
计算机网络
进程间通信方式和他们之间的区别
无名管道:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
有名管道;有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
消息队列通信:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号量通信:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
信号:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存通信:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
套接字通信:套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
线程同步
多个线程可能同时使用一个共享资源,使用线程锁解锁问题
进程线程区别
-
一个程序至少有一个进程,一个进程至少有一个线程
-
线程的划分尺度小于进程,多线程的并发性优于多进程
-
进程在执行过程中拥有独立的内存单元,多个线程共享内存,极大的提高了运行效率
-
线程不能独立执行,必须依赖已存在的应用程序
-
多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但是操作系统并没有把多个线程看作多个独立应用,来实现进程的调度和 管理及资源分配。这是两者最大的区别
-
优缺点:线程开销小但不利于资源保护与管理,同时线程适合在SMP机器上运行,而进程可以跨机器迁移
(1) 进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)
(2) 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
(3) 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
(4) 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
线程同步、互斥
信号量和互斥锁;信号量适用同时可用的资源为多个的情况;互斥锁适用于线程可用的资源只有一个的情况.
互斥锁:互斥锁是用加锁的方式来控制对公共资源的原子操作(一旦开始进行就不会被打断的操作)
(1) 互斥锁只有上锁和解锁两种状态。互斥锁可以看作是特殊意义的全局变量,因为在同一时刻只有一个线程能够对互斥锁进行操作;只有上锁的进程才可以对公共资源进行访问,其他进程只能等到该进程解锁才可以对公共资源进行操作。
(2) 信号量:信号量本质上是一个计数器,在操作系统做用于PV原子操作;
P操作使计数器-1;V操作使计数器+1.
在互斥操作中可以是使用一个信号量;在同步操作中需要使用多个信号量,并设置不同的初始值安排它们顺序执行
信号量:初始化信号,让使用的线程进行PV(申请、释放)操作,从而使线程以自己预定的逻辑运行
互斥锁:申请一把锁,使用资源时上锁,在用完之后开锁,让资源在同一时间只有一个线程在使用
死锁【解决方式:顺序加锁】
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
IO文件
IO模型和他们的区别
阻塞、非阻塞、异步通知、多路复用。
fd:标准接口-fopen-c标准-缓存
系统调用:read、write、Unix[linux macos]
网络编程 TCP\OSI[7层]
TCP/IP模型分层/各层作用/各层协议
分为四层。应用层、TCP层、IP层、网络接口层。
各层作用:
应用层:负责提供数据
TCP层:负责安全性问题
IP层:提供端到端的服务
网络接口层:根据当前使用的不同的物理介质。将数据转换成不同的信号发 送出去
TCP/UDP区别/优缺点
TCP是面向连接的、流的、安全可靠的
UDP是面向无连接的、数据包的、不安全可靠的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SllSCtSX-1605151651988)(https://i.loli.net/2020/11/12/bgaO1C6cvQIAPkz.png)]
窗口【流量控制】
TCP头里的主要用于流量控制、防止数据溢出
TCP如何保证安全可靠的?
多播/广播区别
广播是所有人能听到,在组播中想接收消息需要进入特定组
三次握手、四次挥手
三次握手防止第二条链接出现 四次挥手断开所有链接
ARP协议做啥的
根据IP地址获取物理地址的协议
ICMP协议
是TCP/IP协议族下的一个子协议,用于在IP主机、路由器之间传 递控制消息
套接字类型
流式套接字 数据报套接字 原始套接字
多路复用
select 监控有上限 无法获取有异常发生的进程
Poll 监控无上限 无法获取有异常发生的进程
Epoll 监控无上限 可以获取有异常发生的进程
select、poll、epoll区别
服务器并发
-
多进程/多线程 实时性高 资源占用大
-
多路复用 实时性差 资源占用小
服务器为每一个客户机请求创建一个线程来为其服务,但多个线程会影响服务器端的运行效率
对方挂掉状态获取【recv返回0】
对方挂掉通过返回值获取状态
字节序转换、结构体对齐
字节序问题, 发送之前主机转网络字节序,接手之后网络转主机
大端小端的问题 各个系统之间有差异 网络上为小端 小端为 低对低 高对高 大端相反
路由器工作在那一层
路由器工作在IP(网络)层,负责寻址;交换机工作在【网络接口、网络层】
网络传送数据注意事项【结构体对齐、字节序】
系统移植
1. *内核/uboot使用的是3.14*
2. ****Uboot给内核传参****:uboot会给内核传很多参数如:串口 RAM videofb MAC 地址等,内核会读取并解析这些数据,两者减通过tag结构体传递参数
3. ****Uboot的几个阶段****:一共分为三个阶段
-
最开始为汇编代码 核心硬件的初始化 关闭中断/看门狗/内存的初始 化 设置堆栈为后面的C开空间 #自拷贝#
-
大部分硬件的初始化 鼠标键盘网卡 自动/交互 加载操作系统
-
设置uboot将要传给内核的tag,以及解析uboot头里包含的信息最终跳转到内核,将主控权交给内核
自拷贝:uboot将自己从flash拷贝到RAM中 然后去RAM 中执行 速度问题
交互模式:用户可以配置uboot的工作方式
自动模式:uboot自动去加载操作系统
4. ****内核移植过程****:
-
搭建交叉开发环境
-
bootload的选择和移植
-
内核的配置、编译和移植
-
根文件系统的制作
5. ****文件系统****:nfs
驱动开发
1. ****用户和内核的通信方式****:
*API:函数接口
*proc文件系统:proc是一种虚 拟文件系统,有别于普通文件,虚 拟文件都是动态创建的
*sysfs文件系统:proc和sysfs可以作为内核和用户交互的手段
netlink: netlink套接字用以实现用户进程与内核进程通信的一种特 殊进程间通信方式。好处***①*使用自定义协议完成数据交换,不需要 添加一个文件*②*支持多点传输*③*支持内核优先发起会话*④****异步通信,支持缓存机制
*文件:较笨拙的方法,内核往文件写数据,用户读数据
*系统调用:将内核空间的地址映射到用户空间
*信号:从内核向进程发送信号
2. ****用户到内核的过程****:
3. ****内核同步机制和区别****:
*原子变量:多用于计数的受保护的值
*自旋锁:没有资源时会一直轮询 反应较快但消耗CPU
*互斥锁:没有资源会睡眠不会消耗CPU
*中断屏蔽:中断拥有硬件优先级 即使上锁也无法阻止中断
4. ****系统启动流程****:
*加载内核:
*启动初始化:
*确定运行级别:
*加载开机启动程序:
*用户登陆:
*进入login shell:
*打开non-login shell:
5. ****中断响应流程****:保护断点 寻找中断入口 执行中断程序 之后恢复现场继续执行
6. ****中断下半部分有哪些/区别****:
-
软中断:内核使用 不用太关心
-
tasklet:驱动使用 基于软中断实现 软中断>tasklet>进程不能睡眠 执行 紧急耗时任务
-
工作队列:可以睡眠 执行紧急耗时任务 内核中有一个专门的线程,顺 序执行这些工作队列
7. ****Kmalloc/vmalloc的区别****:两者都用于内核
-
Kmalloc:分配的资源32b–128k 且物理地址和虚拟地址都 连续访问 效率很高
-
Vmalloc:申请的空间可以很大 从内存中捡漏组成一个你需要的空间 物理地址可能不连续虚拟地址连续 效率低
8. *\Linux有哪些设备/区别*:
-
字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设 备内存中的某一个数据,读取数据需按先后
-
块设备:指可以从设备的任意位置读取一定长度数据的设备如硬盘
-
网络设备:任何网络事务都通过一个接口来进行,能够与其它主机交 换数据
9. ****内核调试方法有哪些****:点灯 printk 日志系统 OOPS proc+sysfs文件系统
10. ****怎么操作寄存器****:ioremap将物理地址映射为虚拟地址 writel/readl 用完 需要释放
11. ****总线是啥****:链接设备和驱动的中间人
-
串行总线:数据同时发送 没有固定格式 传输速度快 但传输成本高不适合 远距离使用
-
并行总线:数据只能各位逐次收发 速度较慢 但便于远距离传输
-
以下三种都为串行总线,都是常见的低速板级通信协议
12. ****I2C****:有两根线:分别时SDA(数据)、SCL(时钟),也是一种同步传输协议。 主机开始 信号后,先发送7bit的地址位和1bit的读写位,每个从机都有 自己的I2C地址,当发现该条指令是发给自己的,就会拉低SDA线(即回复 ACK信号),然后主机发送或接受数据,结束时主机发送停止消息
13. ****SPI****:有四根线:分别时CS(片选)、MOSI(主发从收)、MISO(从发主收)、 CLK(时 钟),是一种同步传输协议。在启动传输千,需要先拉低(根据具体 芯片决定)对应从机CS管脚,传输完成后再拉高,从机上的SPI模块进入休眠
14. ****UART****:一般由TXD、RXD、GND三根线组成,是一种异步传输协议。主从机 均可自由收发数据,但是UART没有时钟线,所以需要提前约定对应的波特 率,这是一种和简单的传输协议,因为没有时钟线通信时要求双方都要有相 同的波特率,也就是要求要有自己的时钟源
************I2C和SPI都是同步协议且都有时钟线,在同一条总线上可以挂多个从设 备,但是I2C的从设备是根据地址来区分的,SPI的从设备是通过CS(片选) 线来区分的,所以SPI总线上每多一个从设备就要多一条线作为片选线,而 I2C只要地址不冲突就可以挂载多个设备,所以在一对多通信时I2C更具优 势,但是I2C总线的速度要低于SPI
15. *内核中的文件:*
*bin 二进制的可执行命令
*boot 存放系统启动时需要用到的程序
*dev 设备挂载文件
*etc 系统管理和配置文件
*lib 动态链接共享库
*home 用户目录
*root 管理员目录
16. *进程的启动和终止方式:*
-
手动启动:用户在终端发出命令,直接启动一个进程
①前台启动 ②后台启动
-
调度启动:系统根据系统资源和进程占用资源情况,事先进行调度安排, 制定任务运行的时间、场合,到时候系统自动完成该任务
议。 主机开始 信号后,先发送7bit的地址位和1bit的读写位,每个从机都有 自己的I2C地址,当发现该条指令是发给自己的,就会拉低SDA线(即回复 ACK信号),然后主机发送或接受数据,结束时主机发送停止消息
13. ****SPI****:有四根线:分别时CS(片选)、MOSI(主发从收)、MISO(从发主收)、 CLK(时 钟),是一种同步传输协议。在启动传输千,需要先拉低(根据具体 芯片决定)对应从机CS管脚,传输完成后再拉高,从机上的SPI模块进入休眠
14. ****UART****:一般由TXD、RXD、GND三根线组成,是一种异步传输协议。主从机 均可自由收发数据,但是UART没有时钟线,所以需要提前约定对应的波特 率,这是一种和简单的传输协议,因为没有时钟线通信时要求双方都要有相 同的波特率,也就是要求要有自己的时钟源
************I2C和SPI都是同步协议且都有时钟线,在同一条总线上可以挂多个从设 备,但是I2C的从设备是根据地址来区分的,SPI的从设备是通过CS(片选) 线来区分的,所以SPI总线上每多一个从设备就要多一条线作为片选线,而 I2C只要地址不冲突就可以挂载多个设备,所以在一对多通信时I2C更具优 势,但是I2C总线的速度要低于SPI
15. *内核中的文件:*
*bin 二进制的可执行命令
*boot 存放系统启动时需要用到的程序
*dev 设备挂载文件
*etc 系统管理和配置文件
*lib 动态链接共享库
*home 用户目录
*root 管理员目录
16. *进程的启动和终止方式:*
-
手动启动:用户在终端发出命令,直接启动一个进程
①前台启动 ②后台启动
-
调度启动:系统根据系统资源和进程占用资源情况,事先进行调度安排, 制定任务运行的时间、场合,到时候系统自动完成该任务