嵌入式基础知识总结

一.C语言

1.数据类型
(1)基本数据类型(int\char\flaot)
(2)数组
(3)指针
(4)函数
(5)结构体、共用体、枚举

2.printf(“%-m.nf”,chang);
-表示左对齐,默认是右对齐
m表示输出数据所占的宽度,n表示小数点后保留n位
printf(“%-md“,num);整数不能控制精度
scanf(“%4f%3f”,&chang,&kuan);scanf只能控制宽度,不能控制精度

3.getchar()替换空格 %*c 忽略一个字符

4.循环

while语句
        (1).条件初始状态
        while((2).条件判断)
        {
                (4).循环体
                (3).条件更新
        }
do while语句
        (1).条件初始状态
	do
	{
		(2)循环体;
		(3)条件更新;
	}
	while((4)条件判断)
for语句
        for((1).条件初始状态;(2).条件判断;(3)条件更新)
        {
                (4).循环体;
        }	

5.continue; //结束本次循环,继续执行下一次循环
break; //跳出循环和CASE语句

6.程序的编译要经历预处理,编译,汇编,链接四个阶段
(1).预处理
  gcc -E test.c -o test.i
(2).编译
  gcc -S test.i -o test.s
(3).汇编
  gcc -c test.s -o test.o
(4).链接
  gcc test.o -o test

7.自定义头文件的使用
vim main.h
条件编译,避免重复引入头文件

 #ifndef _MAIN_H 
 #define _MAIN_H
 (1)宏定义 #define FIRST 3500
 (2)类型定义
 (3)枚举定义
 (4)函数声明
  float add(float,float)
 #endif

#include “main.h”和 #include <main.h>
“”和<>的区别
“”:编译器从用户的工作路径开始搜索头文件
<>: 编译器从标准库路径开始搜索头文件

8.make 工程管理工具
makefile
目标文件:依赖文件
(tab) gcc 依赖文件 -o 目标文件
make 命令执行时,需要一个 Makefile 文件,以告诉 make 命令需要怎么样的去编译和链接程序
make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序

9.GDB调试程序

 gcc  -g demo.c -o demo
 gdb demo
 b main  (breakpoint)设置断点
 r        (run)运行
 n        (next)下一步(不进函数)
 s        (step) (进函数)
 p  i     (print)
 q        (quit)

10.定义数组
数据类型 数组名[元素的个数];//元素的个数必须为常量
float arr[N]={78,89};
(1).部分初始化
若对数组进行部分初始化,其他未初始化的值默认为0;
利用该特点可以清空数据
float arr[N]={0};
(2).全部初始化,如果你对数组元素进行全部初始化,可以省略元素的个数,
那么元素的个数具体由后面的赋值个数所决定
float arr[]={78,89,90,67,56,76,75,74,80,34,87,88};

11.什么是指针? 指针是个数据类型,保存地址的数据类型
什么是指针变量? 保存地址的变量是指针变量
如何去定义一个指针变量
定义一个指向int的指针
int * pi;野指针
int * pi=NULL; 空指针
如果你操作野指针或空指针都会异常结束 ,要么是段错误,要么乱码

指针和const
int i=5;
const int * pi=&i;//pi指向的空间不可改 *pi=6; error
int const * pi;
//以上两个是等价的
int * const pi=&i; //pi这片空间不可改
const星左定指向,星右定内容

//定义一个指向int的指针
int * pi=NULL; //栈区所有的局部变量,形式参数
//参数:你要分配空间的字节数
//返回值:void * 任何数据类型的地址
pi=(int *)malloc(sizeof(int));
free(pi);//将pi指向的空间进行释放
pi=NULL;

定义一个指向函数的指针变量(函数指针)该函数的类型返回值为float,参数列表为(float,float);
float (* pfun)(float,float);
指针函数:返回值为指针称为叫指针函数
定义一个指针函数 float * pfun(float,float);

(1.)定义一个指向int的指针
int * pi=NULL;
(2.)定义一个指向char的指针
char * pc=NULL;
(3.)定义一个指向int的指针的指针
int ** ppi=NULL;
(4.)定义一个指向数组的指针,该数组为一个长度为5的int数组
int (* par) [5]=NULL;

指针的作用:
(1).指针可以间接改变指向空间的值
int i=12,*pi=&i;
*pi=5;
(2).如果一个函数要返回1个以上的值,可以通过参数的形式返回

 swap2(&a,&b);
 void swap2(int * pa,int * pb)
 {
	int tmp=0;
	tmp=*pa;
	*pa=*pb;
	*pb=tmp;
}

12.定义一个指向int的指针
int * pi=NULL;
定义一个长度为5的int数组
int arr[N]={12,78,90,34,15};

pi=arr;
arr+i<=>pi+i<=>&arr[i]<=>&pi[i]
*(arr+i)<
=>(pi+i)<=>arr[i]<=>pi[i]
指针的运算
pi+1<===> pi+1
sizeof(int)

13.*的三种用法:
(1).定义变量,它表示指针这种数据类型
int * pi;
(2).作为单目运算符,它表示取值运算符
*pi
(3).作为双目运算符,它表示乘号;

14.字符串常量的特点:
(1).有一个’\0’结束标志
(2).字符串本身表示第一个字符的首地址
(3).常量中的值不能被改变
strlen(字符串首地址)
strcpy(字符串1首地址,字符串2首地址);
strcat(字符串1首地址,字符串2首地址);
strcmp(字符串1首地址,字符串2首地址);

字符指针和字符数组的区别:
char * pc=“hello”;
char str[10]=“hello”;
(1).char * pc=NULL;
pc=“hello”;
char str[10];
str=“hello”; (error);常量不能赋值 strcpy(str,“hello”);
(2).sizeof(pc)=4;
sizeof(str)=10
(3).str[0]=‘A’;
*pc=‘A’;(error)
(4).scanf("%s",str);
scanf("%s",pc); (error);

15.共用体
(1).共用体所有成员的首地址是一致的,
(2).共用体所在的内存数是所有成员中所占内存最大的那个
//可以通过共用体去判断该计算器属于大端序还是小端序
大端序:低位字节放在高地址
小端序:低位字节放在低地址

方法1:
   #include<stdio.h>
   //定义一个共用体
   union un
   {
 	char str[4];
	int i;
   };
   int main(void)
   {
	//为共用体中的i赋值
	union un u;
	u.i=0x12345678;
        if(u.str[0]==0x12)
        {
           printf("大端序\n");
         }else
        {
           printf("小端序\n");
        }	
        return 0;
   }   

方法2:
   #include<stdio.h>
    int main(void)
    {
     int a=0x12345678;
     //定义一个字符型指针变量
     char * p=(char *)&a; //将int *强制转换为char *     
     if(*p==0x12)
     {
        printf("大端序\n");
     }
     else
     {
        printf("小端序\n");
     }
     return 0;
    }          

16.变量作用域:
全局变量:在函数外定义的变量 作用域:从定义变量开始,该文件结束
局部变量:在函数内定义变量,形式参数 作用域:{}之内

17.extern int a; //引入外部文件中的变量a
用于修饰变量或函数,表明该变量或函数都是在别的文件中定义的,提示编译器在其他文件中寻找定义。

static+全局变量 表示隐藏,只允许在本文件被使用
static+局部变量 延长生命周期
static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;

18.递归函数:自己调用自己
斐波那契数列

19.存分段:
代码段、数据段、堆段、栈段
代码段 : 代码
数据段 : 全局变量、常量、静态变量
堆 段 : 动态申请(malloc, realloc, calloc),动态释放(free)
栈 段 : 局部变量、参数

20.memcpy 用法
原型 : extern void * memcpy(void * dest, void * src, unsigned int count);
用法:#include<stdio.h>
功能:由src所指内存区域复制count个字节到dest所指内存区域
说明:src和dest所指内存区域不能重叠,函数返回值向dest的指针

21.shell编程:命令的有序集合
C语言:编译性语言 shell语言:解释性语言
#!/bin/bash

22.软件测试都有那些种类?
黑盒:针对系统功能的测试 白盒:测试函数功能,各函数接口

23.Heap与stack的差别:
Heap是堆,stack是栈。
Stack的空间由操作系统自动分配/释放,Heap上的空间手动分配/释放。
Stack空间有限,Heap是很大的自由存储区
stack先进后出,heap先进先出
C中的malloc函数分配的内存空间即在堆上,C++中对应的是new操作符。
程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时参数的传递也在栈上进行

24.写一个“标准”宏,这个宏输入两个参数并返回较小的一个
#define Min(X, Y) ((X)>(Y)?(Y):(X))//结尾没有;

25.关键字const有什么含意?
表示修饰的数据类型不可修改

26.Volatile修饰符告诉编译程序不要对该变量所参与的操作进行优化,强制编译器每次从内存中取得该变量的值

27.什么是引用,引用与指针有什么区别?

  1. 引用必须被初始化,指针不必。
  2. 引用初始化以后不能被改变,指针可以改变所指的对象。
  3. 不存在指向空值的引用,但是存在指向空值的指针。

28.什么是预编译?何时需要预编译?
预编译又称预处理,是整个编译过程最先做的工作,即程序执行前的一些预处理工作。主要处理#开头的指令。如拷贝#include包含的文件代码、替换#define定义的宏、条件编译#if等。.
何时需要预编译:
  1、总是使用不经常改动的大型代码体。
  2、程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。

29.sizeof(void)等于1 sizeof(void *)等于4
sizeof是运算符 strlen是函数

 #include <stdio.h>
 #include <string.h>
 int main(void)
 {
  int a = 0, b = 0;
  char str[] = "hello";
  a = sizeof(str);
  b = strlen(str);
  printf("a = %d,b = %d\r\n",a,b);//6,5
  return 0;
 }

30.七大排序算法
1.交换排序:交换排序的基本思想都为通过比较两个数的大小,当满足某些条件时对它进行交换从而达到排序的目的
 1)冒泡:比较相邻的两个数,如果前者比后者大,则进行交换。每一轮排序结束,选出一个未排序中最大的数放到数组后面。
最差时间复杂度为O(n2),平均时间复杂度为O(n2)。稳定性:稳定。辅助空间O(1)。
  2)快排:选取一个基准元素,通常为数组最后一个元素(或者第一个元素)。从前向后遍历数组,当遇到小于基准元素的元素时,把它和左边第一个大于基准元素的元素进行交换。
在利用分治策略从已经分好的两组中分别进行以上步骤,直到排序完成。下图表示了这个过程。
2.插入排序:
  1)直接插入:和交换排序不同的是它不用进行交换操作,而是用一个临时变量存储当前值。当前面的元素比后面大时,先把后面的元素存入临时变量,前面元素的值放到后面
元素位置,再到最后把其值插入到合适的数组位置。
  2)希尔排序:基本思想为在直接插入排序的思想下设置一个最小增量dk,刚开始dk设置为n/2。进行插入排序,随后再让dk=dk/2,再进行插入排序,直到dk为1时完成最后一次插入排序,
此时数组完成排序。
3.选择排序:
  1)直接选择:依次选出数组最小的数放到数组的前面。首先从数组的第二个元素开始往后遍历,找出最小的数放到第一个位置。再从剩下数组中找出最小的数放到第二个位置。
以此类推,直到数组有序。
  2)堆排序:先把数组构造成一个大顶堆(父亲节点大于其子节点),然后把堆顶(数组最大值,数组第一个元素)和数组最后一个元素交换,这样就把最大值放到了数组最后边。把数组长度n-1,
再进行构造堆,把剩余的第二大值放到堆顶,输出堆顶(放到剩余未排序数组最后面)。依次类推,直至数组排序完成。
4.归并排序:归并算法应用到分治策略,简单说就是把一个答问题分解成易于解决的小问题后一个个解决,最后在把小问题的一步步合并成总问题的解。这里的排序应用递归来把数组分解成一个个小数组,
直到小数组的数位有序,在把有序的小数组两两合并而成有序的大数组。

31.用宏定义写出 swap(x,y)
#define swap(x, y)
x = x + y;
y = x - y;
x = x - y;

二.LinuxC 高级

  1. 进程管理
     (1) .查看进程 ps
       ps -aux
       top 监视进程,3秒变一次
       pstree 以树状图显示进程
      (2) .杀死进程
       kill -l
       kill -9 进程的ID号

2.文件系统
Ext4
df -T 查看磁盘状态
tar打包工具
-c打包
-v 显示过程
-f指定文件名
-x解包
-z 自动调用gzip
-j 自动调用bzip2
-C 指定路径

3.硬连接:你改变名字并不影响两个文件的关系
软连接:当你改变名字,关系就不存在了

三.数据结构

1.顺序表特点:

  1. 地址连续,大小固定
  2. 除了第一个元素外,其它元素都有前驱。
    除了最后一个元素外,其它元素都有后继。
  3. 访问方便,通过下标来访问
  4. 删除和插入不方便,可能需要移动元素
 定义一个数据类型来描述顺序表
 方法1:
 #define SIZE 20
 typedef int data_type;
 struct list 
 {
  data_type * pData;
  uint count;
 };
 结构体中的这个指针在使用前需要申请空间。《复杂》
 方法2:
 #define SIZE 20
 typedef int data_type;
 struct list 
 {
  data_type data[SIZE];
  uint count;
 };

  创建表
   LIST * createList(void);
  销毁表
   void destroyList(LIST * pList);
  增加
   int insertToList(LIST * pList, int offset, dataType newItem);
  删除
   int deleteFromList(LIST * pList, int offset, dataType * pDelData);
  修改
   int updateList(LIST * pList, dataType oldItem, dataType newItem);
   int updateList(LIST * pList, int offset, dataType newItem);
  查询
   int searchList(LIST * pList, dataType searchItem);
  判空   
   int  isEmpty(LIST * pList);
  判满
   int  isFull(LIST * pList);

2.链表的特点:
  1.地址可以不连续
  2.大小不固定
  3.访问不方便,一般要通过头结点开始向后挨个结点进行访问
  4.插入和删除方便,不需要移动元素

 定义一个数据结构来描述链表
 struct employee
 {
  char name[SIZE];//员工姓名
  char cardNum[SIZE];//卡号
  float salary;//工资
 };
 typedef struct employee dataType; //给类型起个别名
 struct list 
 {
  dataType data; //数据域
  struct list * pNext;//指针域
 };
 typedef struct list LIST;

四.文件IO和标准IO

1.linux一切皆文件,按类型分为
普通文件、目录、字符设备、块设备、有名管道、套接口、符号链接

2.文件IO和标准IO的区别:
  1.文件IO来源于操作系统,标准IO来源于C库
  2.文件IO操作文件描述符,标准IO操作流指针
  3.文件IO不带缓存,标准IO带缓存

文件描述符
非负整数,内核中用来标识打开的文件,0-1023
标准输入输出错误,0 1 2

五. 进程与线程

1.进程
进程是一个独立的可调度的任务
进程是一个程序执行一次的过程
进程是程序执行和资源管理的最小单位

进程与程序区别
1 程序是一个可执行的二进制文件,静态
2 进程是一个程序执行过程,动态

2.fork
1 fork函数,创建一个子进程,子进程拷贝父进程数据,两个进程在调度时先后顺序不确定。
2 重点:返回值,子进程拷贝了父进程数据,在执行时从fork调用后开始执行,子进程fork返回值为0,父进程中fork返回子进程pid。

vfork
创建进程同fork,但只创建进程不拷贝父进程数据,需要改变父进程数据时才拷贝,写时拷贝

exit和_exit,入参进程退出状态,int类型
注意exit退出时清理IO缓冲区,_exit退出时不清理IO缓冲区

wait和waitpid
wait用于等待子进程退出并获取状态,如子进程未退出,则阻塞在wait处
waitpid用于等待指定的子进程退出,并获取状态,退出状态需要有exit或_exit指定当设置为WNOHANG时,不阻塞,如果子进 程未退出,则返回为0,子进程退出返回子进程ID
退出状态需要由WEXITSTATUS获取。

exec函数族
一共6个函数,但目的只有一个:执行一个可执行文件,并传递参数,参数分为命令行参数和环境变量
命令行参数传递方式
1 l(list),以逗号分隔,一个一个列出,例如 “aaa”,“bbb”,“ccc”,NULL
2 v(vector),字符串数组,以NULL结尾
环境变量传递
v只有一种方式,字符串数组,以NULL结尾。
exec函数族中,l–list v–vector e–环境变量 p–PATH环境变量中查找可执行程序,只给出文件名即可

3.孤儿进程:父进程先于子进程结束,此时子进程的父进程为init进程,孤儿进程退出有init进程处理,无危害
 僵尸进程:子进程先于父进程结束,父进程未处理子进程退出状态,此时子进程所占用的空间已经释放但仍占用一个task_struct结构体
僵尸进程在开发过程中一定要避免,因为task_struct也会占用一定的系统资源。
避免方式:
  1 可以使用wait或waitpid方式避免
  2 忽略子进程退出信号 signal(SIGCHLD, SIG_IGN)

4.守护进程
1 守护进程通常用于提供某些服务,例如163服务器提供的网页服务
2 创建步骤
  1 fork,父进程退出,子进程成为孤儿进程
  2 setsid 产生一个新会话,创建一个进程组,进程的组长进程是调用setsid的进程
   *此时以脱离终端,不会因为终端的关闭而影响到守护进程,已经是一个守护进程。
  通常还需要以下几步操作(可选)
  3 改变工作目录 chdir("/tmp") 或根目录
  4 文件掩码 umask(0)
  5 关闭文件描述符 getdtablesize() 返回一个进程能打开的文件最大值

5.线程:线程是一个轻量级的进程,线程共享创建它的进程的地址空间,是操作系统调度执行的最小单位
创建线程 pthread_create
第一个入参为要创建的线程
第二个入参为线程属性 通常使用NULL
第三个入参为线程处理函数 ,注意函数类型
第四个入参为线程处理函数入参,入参为void*类型,在线程函数中使用时,
需要做对应的转换和数据拷贝工作,其目的是做到数据独立于main函数(主线程)和线程函数互不影响。
拷贝的数据需要考虑释放
成功返回0

等待线程结束和线程信息返回
pthread_join
参数1 等待的线程
参数2 线程的结束信息,注意参数为void
成功返回0

ptherad_exit
参数1 void *类型,其返回数据由pthread_join获取

互斥
1 目的是保存临界资源,资源有可能是一段代码的连续执行,也有可能是全局变量,
但互斥的目的是保证同一时刻只有且只能有一个操作在修改临界资源
2 线程互斥锁
pthread_mutex_init 初始化锁
pthread_mutes_lock 加锁
pthread_mutex_unlock 解锁

同步:强调的是顺序
1 信号量 P V 操作
P 信号量值减1 , V 信号量值加一
在sem_wait未申请到信号量资源时,阻塞,一直等到申请到信号量资源
2 相关函数
sem_init 初始化信号量
参数1 信号量
参数2 0:用于线程,1 用于进程
参数3 信号量初始值
sem_wait p操作
sem_post v操作

6.进程间通信
管道是一种半双工的通信方式、数据只能单向流动
管道和管道之间的通信就是一个文件
(1) 无名管道
具有亲缘关系的进程
读写端固定,fd[0] 读, fd[1] 写
pipe read write close
入参 一个2个成员的描述符数组
返回值 0成功, -1失败
特点:具有亲缘关系的进程间,单工,数据在内存中
(2)有名管道
双工(是通过两个管道文件实现双工通信的)
无名管道只能用于具有亲缘关系的进程之间,这就限制了无名管道的使用范围
有名管道可以使互不相关的两个进程互相通信。有名管道可以通过路径名来指出,并且在文件系统中可见
进程通过文件IO来操作有名管道
有名管道遵循先进先出规则
不支持如lseek() 操作(文件中没有数据)
向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。
如果读进程不读走管道缓冲区中的数据,那么写操作将会一直阻塞。
有名管道文件类型 p
mkfifo open write read close
参数1 创建的fifo文件名
参数2 创建fifo文件的权限 , 例如,0666 ,创建后是0664 0666 &(~umask)
特点:可用于任意进程间,双工,有文件名,数据在内存中
(3)信号
  1 执行过程 信号的默认处理函数
  2 kill
参数1 进程ID
参数2 信号值
raise 给自己发信号
  3 alarm
定时器,参数 设定多少秒后产生SIGALRM信号,此信号默认处理是结束进程
  pause
此函数等待一个信号产生后继续执行,否则挂起进程(此时cpu利用率很低,接近于0)
  4 signal
入参1 信号
入参2 信号处理函数
用于修改信号默认处理函数
  5 atoi 数字字符串转成整型
(4)共享内存
共享内存就是两个不相关的进程之间可以直接访问同一段内存
共享的是内核里的内存3-4g
1 创建 shmget
入参1 key 通过ftok获取
入参2 size 共享内存大小,字节为单位
入参3 shmflg IPC_CREAT | 0666
返回值 共享内存标识符 int类型
2 映射 shmat
入参1 内存标识符,shmget 返回值
入参2 指定的映射地址,通常用NULL,使用shmat返回的映射地址
入参3 0 共享内存可读写
3 取消映射 shmdt
入参1 shm_at映射的地址
4 删除 shmctl
入参1 内存标识符
入参2 IPC_RMID
入参3 IPC_RMID时给NULL
查看共享内存命令 ipcs
(5) 消息队列
消息队列提供了一个进程向另一个进程发送一个数据块的方法
1 创建
msgget
入参1 key,用ftok创建
入参2 权限, IPC_CREAT | 0666
返回值 成功返回 消息队列ID,失败返回-1, 可用ipcs命令查看
2 发消息
msgsnd
入参1 消息队列ID
入参2 消息结构msgbuf如下:
struct msgbuf
{
long mtype; //消息类型,必须大于0
char mtext[N]}; //消息正文
}
入参3 消息正文大小,需要注意这里
参数3 通常使用0,消息发送完成后返回
返回值, 0 成功, -1 失败
3 收消息
msgrcv
参数1 消息队列id
参数2 接受消息的结构体指针,同自定义的struct msgbuf
参数3 ***消息正文大小
参数4 消息类型
0:接收消息队列中第一个消息。
大于0:接收消息队列中第一个类型为msgtyp的消息.
小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
参数5
通常使用0, 若无消息函数会一直阻塞
4 msgctl
删除消息队列 msgctl(消息队列ID, IPC_RMID, NULL)
(6)信号量
信号量作为进程之间,以及同一进程不同线程之间的同步手段。
1 创建
semget
入参1 key 通过ftok产生
入参2 信号灯集中信号灯的个数
入参3 权限 IPC_CREAT | 0666
返回值 成功:信号灯集ID,失败 -1
2 初始化
semctl 通过此函数可以设置信号灯集中某个信号的初始值
参数1 信号灯集ID
参数2 修改信号灯集中的信号灯编号
参数3 命令
cmd: GETVAL:获取信号灯的值
SETVAL:设置信号灯的值
IPC_RMID:从系统中删除信号灯集合
设置信号灯初值时用到第四个参数
union semun arg,此结构体在<linux/sem.h>中有定义,<sys/sem.h>中无定义
参照man semctl函数说明中关于union semun共同体说明
3 semop PV操作
参数1 信号灯集ID
参数2 struct sembuf
struct sembuf {
short sem_num; // 要操作的信号灯的编号,索引从0开始
short sem_op; // 0 : 等待,直到信号灯的值变成0
// 1 : 释放资源,V操作
// -1 : 分配资源,P操作
short sem_flg; // 0, IPC_NOWAIT, SEM_UNDO
};
参数3 操作的信号灯个数
4 删除
semctl(信号灯集ID, 0, IPC_RMID)
(7)有名信号灯
sem_open
参数1 信号灯名
参数2 O_CREAT 如果不存在则创建,如指定此参数,当有名信号存在情况下忽略参数3和参数4
参数3 0666 权限
参数4 0 / 1 初始值
sem_close
关闭一个有名信号灯
编译 需要加 -lpthread

7.列举几种进程的同步机制
1)原子操作
2)信号量机制
3)自旋锁
4)管程,会合,分布式系统

六.网络编程

1.OSI 7层模型
应用层 表示层 会话层 传输层 网络层 链路层 物理层
TCP/IP协议
应用层 HTTP,FTP,SMTP 在应用层包含了不同类型的应用进程,如:telnet远程登录、FTP文件传输等。
传输层 TCP,UDP 传输层主要为两台主机上的应用程序提供端到端的通信服务。
网络层 IP,ARP,路由器 网际协议(IP)在RFC 971中定义,它同时被TCP和UDP使用,处于OSI参考模型的网络层。
链路层 以太网,网桥

TCP三次握手/建立连接
TCP是面向连接的传输层协议,所谓面向连接就是在真正的数据传输开始前要完成连接建立的过程,否则不会进入真正的数据传输阶段。
TCP的连接建立过程通常被称为三次握手(three-way handshake),过程如下:请求端(通常称为客户)发送一个SYN段指明客户打算
连接的服务器的端口,以及初始序号(ISN)。这个SYN段为报文段1。 服务器发回包含服务器的初始序号的SYN报文段(报文段2)作为
应答。同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。 客户必须将确认序号设置为
服务器的ISN加1以对服务器的SYN报文段进行确认(报文段3)。 这三个报文段完成连接的建立。

TCP四次握手/终止连接
一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独进行关闭。当一方完成它的数据发送任务后就发送一个
FIN来终止这个方向连接。当一端收到一个FIN,它必须通知应用层另一端已经终止了那个方向的数据传送。所以TCP终止连接的过程需要四个过程,称之为四次握手过程。

IP地址
在网络中唯一标识一台主机的符号
IP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与上之后才能区分哪些是网络位哪些是主机位

ipv4 4个字节
分为5类 A B C D E
C:192.0.0.1-223.255.255.254 D:224.0.0.1-239.255.255.254
子网掩码作用:判断收入的数据包是内网还是外网
IP地址转换 inet_addr()

网络协议
通信双方的约定规则
应用层:Telent Ftp Http Smtp DNS
传输层:TCP UDP 作用:传输数据
网络层:IP ICMP IGMP

端口号
非负整数 0-65535 作用:找应用进程
现用:1024-65535

2.基于TCP协议的服务端客户端
基于TCP协议的客户端
socket(协议域,套接字类型,0让socket自动匹配其他协议)(返回文件描述符,指向该socket)(创建流式套接字)
->connect(文件描述符,地址信息结构体struct_sockaddr)(成功返回0)(连接服务端)
send/recv()(IO函数,发送接收数据)

基于TCP协议的服务端
socket
->bind(socketaddr_in)(成功返回0)(给fd绑定本机相关信息)
->listen(fd,连接最大长度处理可能同时出现的几个链接请求)(监听客户端的连接)
->accept(fd,客户端所在主机的连接信息,信息长度)(成功返回新的文件描述符,做IO操作,收发数据)(接收客户端的连接请求)
->send/recv

4.抓包工具wireshark

5.select
1 int select(int n, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);
n 监听的最大文件描述符 + 1
read_fds 要读的文件文件描述符的集合
write_fds 要写的文件文件描述符的集合
except_fds 监听的异常的文件描述符集合
timeout 超时设置
返回值:准备好的描述符总个数

6.基于UDP协议的客户端和服务器
服务器
socket
->bind(可选)
->recvfrom
->sendto
客户端
socket
->bind(可选)
->sendto
->recvfrom

7.I/O模型
阻塞 非阻塞 多路复用I/O(select) 信号驱动

8.sqlite

  1. 打开数据库连接sqlite3_open用法
    int sqlite3_open(const char *filename, /*Database filename (UTF-8) */sqlite3 ppDb / OUT: SQLite db handle /);
    用这个函数开始数据库操作。需要传入两个参数,一是数据库文件名,比如:E:/test.db。文件名不需要一定存在,如果此文件不存在,sqlite会自动建立它。如果它存在,
    就尝试把它当数据库文件来打开。二是sqlite3
    ,即前面提到的关键数据结构。这个结构底层细节如何,你不要管它。 函数返回值表示操作是否正确,如果是SQLITE_OK则表示操作正常。
    相关的返回值sqlite定义了一些宏。具体这些宏的含义可以参考sqlite3.h 文件。里面有详细定义(顺便说一下,sqlite3 的代码注释率自称是非常高的,实际上也的确很高。只要你会看英文,
    sqlite 可以让你学到不少东西)。
  2. 关闭数据库链接sqlite3_close用法
    原型:
    int sqlite3_close(sqlite3 *ppDb);
    ppDb为刚才使用sqlite3_open打开的数据库链接
 sqlite3常用指令
  1. 建立数据表
   create table table_name(field1 type1, field2 type1, ...);
   table_name是要创建数据表名称,fieldx是数据表内字段名称,typex则是字段类型。
  2. 添加数据记录
   insert into table_name(field1, field2, ...) values(val1, val2, ...);
   valx为需要存入字段的值。
  3. 修改数据记录
   update table_name set field1=val1, field2=val2 where expression;
   where是sql语句中用于条件判断的命令,expression为判断表达式
  4. 删除数据记录
   delete from table_name [where expression];
   不加判断条件则清空表所有数据记录。
  5. 查询数据记录
   select指令基本格式:select columns from table_name [where expression];

9.网络编程中设计并发服务器,使用多进程 与 多线程 ,请问有什么区别?
1) 进程:子进程是父进程的复制,它获得父进程数据空间,堆栈的复制品。
2) 线程:相对于进程而言,线程更接近于一个执行体的概念,它可以与同进程的其他线程同享进程资源,但同时拥有自己的栈空间,寄存器,指针(独立的执行序列)。
3) 两者都可以提高程序的并发度,提高程序的运行效率和响应。
4) 线程和进程各有优缺点,线程开销少,但不利于资源管理和保护。而进程恰恰相反,开销大,但对资源有独立掌控权,可更好地管理。

10.TCP与UDP的区别:
1.基于连接与无连接;
2.对系统资源的要求(TCP较多,UDP少);
3.UDP程序结构较简单;
4.流模式与数据报模式 ;
5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。

11.Linux 下的 socket() 函数
在 Linux 下使用 <sys/socket.h> 头文件中 socket() 函数来创建套接字,原型为:
int socket(int af, int type, int protocol);
  1) af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,
例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
大家需要记住127.0.0.1,它是一个特殊IP地址,表示本机地址,后面的教程会经常用到。
你也可以使用 PF 前缀,PF 是“Protocol Family”的简写,它和 AF 是一样的。例如,PF_INET 等价于 AF_INET,PF_INET6 等价于 AF_INET6。
  2) type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字),我们已经在《套接字有哪些类型》
一节中进行了介绍。
  3) protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。

七.QT

信号和槽机制(连接方式4种)
1 按钮转到槽(常用又方便)
2 connect(信号的发送者,发出的信号是什么,信号的接收者,接收者调用的槽函数)(最常用)
3画信号和槽(简单不常用)
4用emit主动发信号
信号和槽的对应关系
一个信号对应一个槽(普遍)
一个信号对应一个信号(较少)
一个信号对应多个槽
多个信号对应一个槽

八.ARM

1.ARM流水线
arm7—冯诺依曼结构—3级:取指,译码,执行。
arm9—哈佛结构—5级:取指、译码、执行、访存、回写。

九.Linux底层驱动

1.驱动框架
应用程序的执行依赖于操作系统,需要调用Linux操作系统的库函数来实现。
应用程序不能直接操作硬件
Linux的体系架构使得系统更稳定可靠
Linux驱动包含三种:字符设备驱动、块设备驱动、网络设备驱动

驱动程序是联接操作系统和硬件之间的桥梁
•GPIO(General Purpose Input Output Port),通用输入输出口
通过设置GPIO的高电平或者低电平来实现对外设的操作
GPIO很多是复用的
程序对寄存器操作即可实现对GPIO的操作

•内存管理单元(MMU)
•Linux把设备看成了文件( open, read, write, ioctrl,close)
•Linux字符设备驱动的编程思想:做一组设备对应的驱动函数
•设备节点(设备文件)
•举例说明上层应用程序如何对某个外设进行操作
•举例说明底层驱动的编写方式以及如何注册到系统(register_chrdev())
•MISC杂项设备(混杂设备)
•模块(module)

• 学会查看开发板原理图
• 学会查看处理器的数据手册(datasheet)
在Datasheet中找到相关寄存器的章节,并确定物理地址
通过编写程序来实现对该寄存器的操作,最终控制相关硬件

• 对外部设备操作的步骤(三部曲)
– 通过原理图找到设备连接的PIN脚
– 根据该PIN脚找到控制这个引脚的相关寄存器,并找到寄存器对应的物理
地址
– 最后,通过编写程序来实现对该设备的操作

实现一个嵌入式Linux设备驱动程序的大致流程如下:
(l)查看原理图,理解设备的工作原理。
(2)定义主设备号。设备由一个主设备号和一个次设备号来标识。主设备号唯一标识了设
备类型,即设备驱动程序类型,它是块设备表或字符设备表中设备表项的索引。次设备号仅
由设备驱动程序解释,区分被一个设备驱动控制下的某个独立的设备。
(3)实现初始化函数。在驱动程序中实现驱动的注册和卸载。
(4)设计所要实现的文件操作,定义file–operations结构。
(5)实现所需的文件操作调用,如read,write等。
(6)实现中断服务,并用request–irq向内核注册,中断并不是每个设备驱动所必需的。
(7)编译该驱动程序到内核中,或者用insmod命令加载模块。
(8)测试该设备,编写应用程序,对驱动程序进行测试。

LED驱动如何编写?
1、首先找到板子的原理图,找到对应的引脚。
2、接着打开数据手册,找到对应的寄存器。
3、开始编写LED驱动程序
4、编写makefile
5、插入模块insmod xxx.ko
6、查询主设备号 cat /proc/devices
7、创建设备节点 mknod /dev/xxx c x x
8、执行应用程序app

如果自己来完成LCD驱动,框架应该怎么写?
     1)分配一个fb_info结构体
       sc3fb_alloc_framebuffer(…)
     2)设置该结构体
       s3cfb_init_fbinfo(…)
     3)初始化硬件
       配置gpio管脚,配置成用于LCD的功能
       配置LCD控制器中的可变参数,以满足时序要求
       极性的设置,根据LCD的性质设置
       申请显存,将申请到的显存的地址告诉LCD控制器,控制器会自己定时去刷新
     4)注册fb_info
        s3cfb_register_framebuffer(…)  
LCD驱动移植:修改寄存器值,即时间,从LCD手册中找与arm芯片手册时序图对应的位置,然后搜索就可以找到这个时间的取值范围。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值