静态库 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