静态库
1.生成目标文件 gcc -c x.c —> x.o
2.把目标文件ar成静态库 ar -r libxxx.a x.p …
3.使用静态库:
gcc .c -I 头文件路径 -lxxx -L 静态库路径
CPATH C_INCLUDE_PATH LIBRARY_PATH
动态库
1.生成目标文件 gcc .c -fpic x.c —> x.o
2.编译成动态库 gcc -shared x.o … libxxx.so
3.使用动态库
gcc .c -I 头文件路径 -lxxx -L 动态库路径
CPATH LIBRARY_PATH
动态程序: LD_LIBRARY_PATH
静态库和动态库的区别:
1.静态库比较小,如果使用静态库的程序执行效率比较高,可执行文件比较大
在编译时会把静态库的寒素代码拷贝到可执行文件中,可执行程序在执行过程中不 依赖静态库
2.动态库比较大,如哦使用动态库的程序执行效率稍低,可执行文件比较小
在编译时,如果调用了动态库中的函数会生成调用指令,等到执行时,再跳到动态库指定位置执行
不管是动态库还是静态库都需要提供头文件
错误处理
#include<errno.h>
errno 错误码 当系统调用出错时会设置errno 程序调用成功时不会置0
通过errno获取错误信息:
1.perror()
2.strerror(errno)
3.%m
环境列表
char **environ;
环境列表是一个字符指针数组,以NULL为结束标识
数组中的每一项,都保存着一个环境配置项的地址
char *getenv(const char *name)
int setenv(const char *name,const char *val,int overwrite)
int putenv(const char *string)
void clearenv()
int unsetenv(const char *name)
进程映像
1.程序是保存在磁盘上的可执行文件
2.运行程序时,需要将可执行文件加载到内存,形成进程
3.一个程序可以同时存在多个进程
4.进程在内存空间中的布局就是进程映像,从低地址到高地址依次为
(1)代码区
(2)数据区
(3)bss区
(4)堆
(5)堆栈缓冲区、加载动态库
(6)栈
(7)命令行参数、环境列表
虚拟内存
1.每个进程都有各自相互独立的4G字节虚拟地址空间
2.用户程序中使用的都是虚拟地址空间中的地址,永远无法直接访问实际物理内存地址
3.虚拟内存到屋里内存的映射由操作系统动态维护
4.虚拟内存一方面保护了操作系统的安全,另一方面允许应用程序使用比实际物理内存更大的地址空间
5.4G进程地址空间分成两部分
(1)[0,3G]为用户空间
(2)(3G,4G]为内核空间
6.用户控件中的代码不能直接访问内核空间中的代码和数据,但可以通过系统调用进入内核态,简介的与系统内核交互
7.对内存的越界访问,获知试图访问没有映射到物理内存的虚拟内存,将导致段错误
8.用户空间对应进程,进程一切换,用户空间随之变化
9.每个进程的内存空间完全独立,不同进程之间交换虚拟内存地址是无意义的
10.标准库内部通过一个双向链表,管理在堆中动态分配的内存
11.虚拟内存到物理内存的映射以页(4K)为单位
内存管理APIs
1.增量方式分配虚拟内存
voId *sbrk(intptr_t increment)
increment取值:内存增量(以字节为单位)
0 - 获取末尾地址。
>0 - 增加内存空间。
<0 - 释放内存空间。
内部维护一个指针,指向当前堆内存最后一个字节的笑一个位置。
2.修改虚拟内存块末尾地址
int brk(void *end_data_segment)
内部维护一个指针,指向当前堆内存最后一个字节的下一个文职
brk函数根据指针参数设置该指针的位置
若发现页耗尽或空闲,自动追加或取消页映射
简单来说,sbrk(类似于malloc)分配内存brk释放内存(类似于free)
3.创建与销毁虚拟内存到物理内存或文件的映射
创建函数mmap
void* mmap (
void* start, // 映射区内存起始地址,NULL系统自动选定,成功返回之
size_t length, // 字节长度,自动按页(4K)对齐
int prot, // 映射权限
int flags, // 映射标志
int fd, // 文件描述符
off_t offset // 文件偏移量,自动按页(4K)对齐
);
成功返回映射区内存起始地址,失败返回MAP_FAILED(-1)。
销毁函数munmap
int munmap (
void* start, // 映射区内存起始地址
size_t length, // 字节长度,自动按页(4K)对齐
);
成功返回0,失败返回-1。
mmap/munmap底层不维护任何东西,只是返回一个首地址,所分配内存位于堆中
mmap返回的地址,是虚拟内存地址,用于映射到文件中,而文件地址是物理内存
文件的大小决定了能够操作虚拟内存的大小
如果文件为空,如果通过mmap映射之后进行读写操作会出现总线错误
文件空洞
在超越文件尾的文件位置写入数据,将在文件中形成空洞
文件空洞不占用磁盘空间,但被算在文件大小中
文件锁
1.读锁(共享锁)
一个文件可以有多个读锁
2.写锁(独占锁/排它锁)
一个文件只能有一个写锁
3.文件锁只在不同进程间起作用
通过锁同步多个进程对同一个文件的读写访问
4.当对一个文件上了读锁之后,不能再对其上写锁,但可以上读锁
对一个文件上了写锁之后,不能再对其上写锁或读锁
进程
1.进程与程序
(1)进程就是运行着的程序
(2)程序存储在磁盘上,包含可执行机器指令和数据的静态实体
进程是处于活动状态的计算机程序
2.进程的分类
(1)进程一般分为:交互进程、批处理进程和守护进程三类
(2)守护进程总是活跃的,一般在后台运行
守护进程一般由系统开机时通过脚本自动激活启动,
或者由超级用户root启动
3.查看进程
(1)ps
(2)ps aux
(3)ps -elf
(4)ps aux|grep a.out
(5)top
4.父进程、子进程、孤儿进程、僵尸进程
(1)在一个程序中,通过fork函数创建进程的进程称为父进程
(2)被父进程创建的进程称为子进程
子进程结束时会向父进程发送SIGCHLD(17)信号,父进程回收子进程资源
(3)父进程先于子进程结束,子进程成为孤儿进程,并被init进程收养
(4)子进程先于父进程结束,但父进程没有回收子进程相关资源,该子进程成为僵尸进程
5.进程标识符(进程ID)
(1)每个进程都有一个非负整数表示的唯一标识,即进程ID/PID
(2)这个ID在任何时刻都是唯一的,但可以重复使用,即当一个进程退出时,其他进程可以使用这个ID
6.创建进程函数 fork
(1)创建一个子进程,失败返回-1,成功返回子进程PID和0
(2)子进程是父进程的副本
拷贝父进程的数据段和堆栈段
共享父进程的代码段
(3)函数调用后父子进程各自运行,运行先后顺序不确定
(4)父子进程共享文件表,即赴京城的文件描述符也会被复制到子进程中,二者共享同一个文件表
7.进程创建函数vfor
功能与fork基本相同,区别为:
(1)调用vfork创建子进程时不复制父进程的地址空间
子进程通过exec函数族,直接启动另一个进程替换自身,提高创建效率
(2)vfork调用后,子进程先执行
8.进程的正常退出
(1)return
(2)exit
① atexit(func) on_exit(func)
进程结束前调用用上述函数注册的函数
② 父进程调用wait/waitpid函数返回status的低8位
9. 进程的异常终止
(1) 调用abort函数,产生SIGABRT信号
(2) 进程接收到某些信号
(3) 最后一个线程对”取消”请求做出响应
信号处理
1.不可靠信号(非实时信号)
(1)小于SIGRTMIN(34)的信号都是不可靠信号
(2)不支持排队,可能会丢失,进程可能只收到一次该信号
(3)进程每次处理完这些信号后,对相应信号的相应被自动恢复为默认动作
除非显示地通过signal函数重新设置一次信号处理程序
2.可靠信号(实时信号)
(1)位于[SIGRTMIN(34),SIGRTMAI(64)]区间的信号都是可靠信号
(2)支持排队,不会丢失
3.信号来源
(1)硬件
(2)软件(程序)
4.信号处理
(1)忽略
(2)终止进程
(3)终止进程同时产生core文件
(4)捕获并处理。当信号发生时,内核会调用一个实现注册好的信号处理函数
5.递送与未决
(1)当信号产生时,系统内核会在其所维护的进程表中,为特定的进程设置一个与该信号相对应的标志位,这个过程称为递送
(2)信号从产生到完成递送之间存在一定的时间间隔,处于这段时间间隔中的信号状态称为未决
6.信号屏蔽
当在进行一些任务时,如更新数据库,不希望进程被某些信号中断,此时可以暂时屏蔽这些信号,使其滞留在未决状态,等任务完成后再处理这些 信号
在信号处理函数的执行过程中,这个正在被处理的信号总是处于信号掩码中
进程间通信
进程间通信方式:
a.简单进程间通信: 命令行参数、环境变量、信号、文件
b.传统进程间通信: 有名管道/无名管道(fifo/pipe)
c.XSL进程间通信: 共享内存、消息队列、信号量
d.网络进程间通信: 套接字
IPC标识
(1)用于本地通信,通过设置一个唯一的IPC键值实现多个进程的会合
(2)使用函数ftok创建一个唯一IPC键值
key_t ftok(const char* pathname , int id);
Key_t key=ftok(“./”,100);
(3)IPC命令
1. 显示
ipcs -m - 显示共享内存(m: memory)
ipcs -q - 显示消息队列(q: queue)
ipcs -s - 显示信号量(s: semphore)
ipcs -a - 显示所有IPC对象(a: all)
2. 删除
ipcrm -m ID - 删除共享内存
ipcrm -q ID - 删除消息队列
ipcrm -s ID - 删除信号量
1.共享内存
(1)创建/获取共享内存
int shmget (key_t key, size_t size, int shmflg);
(2)加载共享内存
void* shmat (int shmid, const void* shmaddr,int shmflg);
(3)卸载共享内存
int shmdt (const void* shmaddr);
(4)销毁/控制共享内存
int shmctl (int shmid, int cmd, struct shmid_ds* buf);
2.消息队列
(1)创建/获取消息队列
int msgget (key_t key, int msgflg);
(2)向消息队列发送消息
int msgsnd (int msqid, const void* msgp,size_t msgsz, int msgflg);
(3)从消息队列接收消息
ssize_t msgrcv (int msqid, void* msgp, size_t msgsz,
long msgtyp, int msgflg);
(4)销毁/控制消息队列
int msgctl (int msqid, int cmd, struct msqid_ds* buf);
3.信号量
(1)创建/获取信号量
int semget (key_t key, int nsems, int semflg);
(2)操作信号量
int semop (int semid, struct sembuf* sops,unsigned nsops);
(3)销毁/控制信号量
int semctl (int semid, int semnum, int cmd);
int semctl (int semid, int semnum, int cmd,union semun arg);
8.网络通信
(1)ISO/OSI七层网络协议模型
①应用层 application
②表示层 presentation
③会话层 session
④传输层 transport
⑤网络层 network
⑥数据链路层 data link
⑦物理层 pyhsical
(2)TCP/IP协议族
a.TCP(传输控制协议)面向连接的服务
b.UDP(用户数据报协议)面向无连接的服务
c.IP(互联网协议)信息传递机制
(3)IP地址
1) IP地址是Internet中唯一的地址标识
A. 一个IP地址占32位,正在扩充至128位。
B. 每个Internet包必须带IP地址。
2) 点分十进制表示法
0x01020304 -> 1.2.3.4,高数位在左,低数位在右。
3) IP地址分级
A级:0 + 7位网络地址 + 24位本地地址
B级:10 + 14位网络地址 + 16位本地地址
C级:110 + 21位网络地址 + 8位本地地址
D级:1110 + 28位多播(Muticast)地址
4) 子网掩码
IP地址 & 子网掩码 = 网络地址
IP地址: 192.168.182.48
子网掩码:255.255.255.0
网络地址:192.168.182
本地地址:48
(4)套接字socket
1)创建套接字
int socket (int domain, int type, int protocol);
2)准备通信地址
IP地址用于定位主机,端口号用于定位主机上的进程
A. 基本地址类型
struct sockaddr
{
sa_family_t sa_family; // 地址族
char sa_data[14]; // 地址值
};
B. 本地地址类型
#include <sys/un.h>
struct sockaddr_un
{
sa_family_t sun_family; // 地址族
char sun_path[]; // 套接字文件路径
};
C. 网络地址类型
#include <netinet/in.h>
struct sockaddr_in
{
// 地址族
sa_family_t sin_family;
// 端口号
// unsigned short, 0-65535
// 逻辑上表示一个参与通信的进程
// 使用时需要转成网络字节序
// 0-1024端口一般被系统占用
// 如:21-FTP、23-Telnet、80-WWW
in_port_t sin_port;
// IP地址
struct in_addr sin_addr;
};
struct in_addr
{
in_addr_t s_addr;
};
typedef uint32_t in_addr_t;
3)将套接字和通信地址绑定在一起
int bind (int sockfd, const struct sockaddr* addr,socklen_t addrlen);
4)建立连接
int connect (int sockfd, const struct sockaddr* addr,socklen_t addrlen);
9.TCP协议
(1)三次握手
(connect)SYN_SENT Client SYN x ---> Server LISTEN(accept)
ESTABLISHED Client <--- ACK x+1 SYN y Server SYN_RCVD
(connect retrun) Client ACK y+1 ---> Server ESTABLISHED
(acceptreturn)
(2)四次分手
(close)FIN_WAIT1 Client FIN m ---> Server CLOSE_WAIT(passive close)
FIN_WAIT2 Client <--- ACK m+1 Server (read return)
TIME_WAIT Client <--- FIN n Server LAST_ACK(close)
TIME_WAIT Clinet ACK n+1 ---> Server CLOSE
(3)面向连接,传输可靠,保证数据的完整性和有序性
每个发送都有应答,若在时间窗口内没有收到应答,则重发
10.UDP协议
面向无连接,传输不可靠,不保证数据的完整性和有序性
发送不需要应答,因此效率高速度快
线程
1.基本概念
(1)线程就是程序的执行路线,是进程的子任务
(2)轻量级,不拥有独立的内存资源,共享进程的代码区、数据区、堆区、环 境变量和命令行参数、文件描述符、信号处理函数、当前目录、用户ID 和组ID等资源
(3)线程拥有自己独立的栈,因此有自己独立的局部变量
(4)一个进程可以同时拥有多个线程
(5)同一个进程的多个线程都在同一个地址空间内活动,因此相对于进程,线 程的系统开销小,任务切换快。
2.线程函数
(1)创建线程
int pthread_create (pthread_t* restrict thread, const pthread_attr_t* restrict attr, void* (start_routine) (void), void* restrict arg);
(2)等待线程
int pthread_join (pthread_t thread, void** retval);
等待thread参数所标识的线程结束,成功返回0,失败返回错误码。
(3)获取线程自身ID
pthread_t pthread_self (void);
成功返回调用线程的ID,不会失败。
(4)比较两个线程的ID
int pthread_equal (pthread_t t1, pthread_t t2);
(5)终止线程
void pthread_exit (void* retval);
3.线程同步
(1)概念
当多个线程同时访问其所共享的进程资源时,需要相互协调,以防止出现 数据不一致、不完整的问题,这就是线程同步。
(2)互斥量(互斥锁) pthread_mutex_t
1)类型
a)普通锁
b)检错锁
c)嵌套锁
d)默认锁
(3)屏障 ptread_barrier_t
(4)读写锁 pthread_rwlock_t
(5)自旋锁 pthread_spinlock_t
(6)信号量 sem_t
(7)条件变量 pthread_cond_t
4.多路复用IO