1. APUE Unix环境高级编程
(1) Unix基础知识: 内核->系统调用->shell和库函数->应用软件
(2) 文件I/O:read函数返回值、进程的文件描述符表、文件共享、inode、fcntl函数
(3) 文件和目录:文件类型(普通、目录、socket、FIFO、PIPE)、文件系统(硬链接、软链接)
(5) 进程环境:进程终止情况、longjmp、volatile用法
(6) 进程控制:fork(返回值、父子进程的文件描述符表)、wait函数、exec系函数
(7) 信号:三种处理方式、常见信号(SIGCHLD、SIGSEGV)及其默认处理方式
(8) 线程同步:进程和线程的异同点、线程回调函数的传参、进程和线程原语对比、线程同步(读写、写写、i++多线程、mutex)
(9) 高级I/O(一):非阻塞I/O、select使用、readn和writen封装、mmap映射
(10) 高级I/O(二):管道、消息队列、共享存储器mmap
(11) 网络套接字:大小端字节序、addrinfo结构体、getaddrinfo函数、tcp_listen和tcp_connect封装
2. Linux系统编程
(1) 系统调用、API和ABI、文件(各种类型简介、inode、软链接、硬链接)、进程简介
(2) 文件I/O:read函数返回值、readn和writen函数封装、文件定位、文件同步、文件截断、多路I/O(select、poll)、Linux内核实现
(3) 标准I/O缓冲:测试最佳I/O缓冲区大小、三种类型缓冲、标准I/O的线程安全、标准I/O的缺点
(4) 高级I/O:聚集I/O(readv、writev性能好、原子性)、epoll(LT和ET)、mmap(优缺点)、I/O调度算法(电梯调度)
(5) 进程管理:进程pid分配方法、fork and exec、copy on write、wait、僵尸进程的回收
(6) 进程调度:Linux进程调度算法(FIFO、最短优先、时间片轮转)、CPU绑定(缓存影响)
(7) 线程:虚拟化抽象(虚拟内存与进程关联、虚拟处理器与线程关联)、多线程好处、多线程i++解析、pthread使用、RAII封装mutex
(8) 文件和目录管理:文件inode节点元数据、目录操作、软链接和硬链接、块设备/dev/null
(9) 内存管理:进程地址空间、动态分配内存(malloc、calloc、brk)、数据对齐、匿名文件映射、变长数组
(10) 信号:信号的3中处理方式、常见的信号
(11) 时间:gettimeofday、sleep休眠、定时器alarm
3. 位、字节、整型
字节序问题:
大端:地址为 阅读顺序:Sun系统 和 网络字节序
小端:地址为 逆阅读序:X86
信息 = Bit位 + 解释方式,无论怎么转换,Bit位是永远不变的,只有解释方式改变了
对于int的使用,参见:Google c++编程规范中int型使用
C语言整型溢出问题by @左耳朵耗子
尽量不要使用无符号数,极易bug:当执行一个运算时,如果一个运算数是有符号的而另一个是无符号的,那么c语言会隐式地将有符号数强制类型转换为无符号数
比如:函数参数类型为size_t, 我传入实参-1,这时-1会隐式的 转换为 最大正数,完全错了
4. 数据对齐、结构体访问、offsetof
结构体数据对齐有2个原则:
(1) 每个成员数据的偏移 必须是 该数据类型大小的整数倍
(2) 整个结构体大小 必须是 最大数据类型大小的 整数倍
数据对齐产生了 内部碎片,为了使内部碎片最小,必须让 最大的数据类型摆放在最前面
如何计算 每个成员数据在结构体内的偏移呢? C语言提供 offsetof宏
#define offsetof(type, member) (size_t)&(((type*)0)->member)
struct test { int val; int a[2]; }; // 结构体访问和数组访问的实质都是 首地址加上对应偏移,然后读取对应数据,注意 取地址 和 取成员 操作的区别 struct test* ptest = NULL; // 结构体首地址为0 fprintf(stdout, "1:%p ", &ptest->val); // 注意:这是读取 结构体成员数据的偏移,直接就是 首地址加上 offset,而不是 先取到数据然后取地址 fprintf(stdout, "2:%d ", ptest->val); // 错误,可以打印地址0的地址值,但是不能访问地址值为0的数据 fprintf(stdout, "3:%p ", &ptest->a); // 正确,首地址加上offset fprintf(stdout, "4:%p ", ptest->a); // 同上,数组名退化为 数组首地址,所以这还是一个取地址操作
具体详见:C语言结构体内的数组和指针by@左耳朵耗子
5. cache 矩阵乘法
CPU Cache示例by@左耳朵耗子
C语言数组行优先存储,当数组大小超过 缓存块大小时,不同的存取方式之间 差别很大,以 A[N][N] * B[N][N] 存储在 C[N][N]中
6. 编译链接
静态编译和动态编译
详见陈硕的 <C++编译链接模型精要>
链接器主要完成两件事:
(1) 符号解析:将每个符号的引用(reference)对应到相应符号的定义(definition)
C程序员应该尽可能使用static属性在模块内部隐藏变量和函数声明,即模块私有
注:使用 readelf命令来查看目标文件的符号表
当linker遇到重名时:
重名Local定义:编译器解决
重名Global定义:强符号(函数名和已初始化的全局变量)和弱符号(未初始化的全局变量)
(2) 重定位:合并不同文件时修改符号地址,将符号的相对地址 改为 绝对地址
Linux系统为动态链接库提供了一个简单的接口,允许应用程序在运行时加载和链接共享库
#include <dlfcn.h> void* dlopen(const char* filename, int flag); // dlopen函数加载和链接共享库filename void* dlsym(void* handle, char* symbol); // dlsym函数的输入参数为dlopen打开的共享库句柄和符号名,返回符号的地址 int dlclose(void* handle);
7. 进程间通信
(1) 管道、FIFO(命名管道)
管道:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系
FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
(2) 信号(Signal)
信号用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)
(3) 报文(Message)队列(消息队列)
消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
(4) 信号量(semaphore)
主要作为进程间以及同一进程不同线程之间的同步手段,是一个计数器,用于多进程对共享数据对象的访问
(5) 共享内存
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问
共享内存是最快的 IPC 方式(因为数据不需要在客户进程和服务器进程之间复制)
使用mmap进行文件内存映射 和 匿名存储映射(将fd设为-1)
(6) socket套接字
套接字接口既可以用于多机之间进程通信,也可以用于单机内部进程通信
8. 进程同步原语
(1) 互斥锁
(2) 条件变量
(3) 读写锁
9.高效拷贝文件