LIinux环境c语言。(一)

一、课程介绍
UNIX/Linux环境C语言,借助学习操作系统的接口的方法来学习、理解操作系统的运行机制以及一些网络协议。

C/C++、数据结构和算法 与平台无关,重点是算法逻辑
UNIX/Linux/Android/iOS 平台相关,系统接口
嵌入式/驱动/移植 硬件相关,硬件接口

环境介绍
内存管理
文件操作
文件管理
信号处理
进程管理
网络通信
线程同步

二、UNIX操作系统
丹尼斯.里奇、肯.汤普逊于1971左右在美国贝尔实验室,使用C语言开发了这款操作系统。

系统的特点是多用户、多任务,支持多种处理器架构,高安全性、高可靠性、高稳定性。
既可以构建大型关键业务系统的商用服务器,也可以构建面向移动终端的、手持设备等相关的嵌入式应用。

三大衍生版本
	System V:银行、电信在使用的服务器系统
	Berkley:MacOS iOS带界面的
	Hybrid:Minix、Linux

三、Linux操作系统
类UNIX系统,免费开源,它指的是系统的内核,凡是使用这种内核的操作系统都叫作Linux系统(发行版),严格意义上讲Linux指的是内核,隶属于GNU工程。
手机、平板电脑、路由器、视频游戏控制台、PC、大型计算机、超级计算机。
标志是一只企鹅,因为企鹅是南极的标志性动物,根据国际公约南极为全人类共同所有,所以Linux使用企鹅作来标志也表明:开源的Linux为全人类共用所有,任何公司或个人无权将其私有。
Minix操作系统是一个微型的类UNIX系统、免费开源,而Linux之父就是在参照这款操作系统,才写出了第一个版本的Linux内核代码。
GNU工程:是自由软件基金会所创立的一个开源组织,基本原则就是共享,主旨是发展出一个有别于商业UNIX的免费且完整的类UNIX系统——GNU Not UNIX。目前Linux内核由它进行维护,所以Linux也叫GNU Linux。
GPL通用公共许可证:允许对某些成果及派生成果重用、修改、复制,对所有人都是自由的,但不能声明做了原始工作,或声明由他人所做。
POSIX标准:Portable Operating System Interface,缩写为 POSIX,统一的系统编程接口规范,它规定了操作系统以接口形式提供的功能的 名字、参数、返回值,它保障了应用程序源码级的可移植性,而Linux完全遵循了这个标准。
版本管理:
早期版本:0.01、0.02、…、0.09、1.0
旧计划:A.B.C
A 主版本号
B 次版本号
C 补丁序号
新计划:A.B.C.D.E
D 构建次数
E 描述信息
特点:
多用户、多任务
遵循GNU/GPL具有开放性
丰富的网络功能
可靠的系统安全
良好的可移植性
发行版:
Debian
Ubuntu
Fedora
Redhat
CentOS
四、GNU编译器
1、支持众多编程语言、平台
2、构建过程(C代码是如何变成可执行文件的)。
预处理:把程序员所编译的C代码翻译成标准的C代码
汇编:把预处理后的C代码翻译成汇编代码
编译:把汇编代码翻译成二进制指令
链接:把若干个目标文件合并成一个可执行文件
3、gcc -v 查看版本
4、文件后缀
.h 头文件
.gch 头文件的编译结果,一般不要保留。
.c 源文件
.i 预处理文件
.s 汇编文件
.o 目标文件
.a 静态库文件
.so 共享库文件
5、参数
-E 预处理
-S 汇编
-c 编译(只生成目标文件)
-o 指定编译结果的名字
-Wall 产生尽可能多的警告
-Werror 把警告当作错误处理
-x 指定编译的语言
-g 生成调试信息
-On 优化等级
-D 编译时定义宏
-l 链接里加库
-I 指定头文件的查找路径,配置环境变量
1、打开 vim ~/.bashrc
2、在文件末尾,添加一行 export C_INCLUDE_PATH=$C_INCLUDE_PATH:NEW_PATH
3、重新加载配置文件 source ~/.bashrc
注意:如果要删除环境变量需要在~/.bashrc文件中删除环境变量后,退出终端重新打开。
考题1:#include <> / #include “” 区别?
考题2:头文件中可以编写哪些内容?
考题3:头文件的作用?
1、说明对应的.c文件的内容有哪些(声明函数、全局变量)。
2、定义结构、联合、枚举、宏。
3、类型重定义。
虽然函数可以隐式声明,但并不一定准确,而且非常有可能造成严重错误。
6、预处理指令
#include 文件包含,区别分""和<>的区别
#define 定义宏常量或或函数
# 把标识符转换成字符串
## 合并标识符
#undef 删除宏
#line 指定当前行的行号
#if
#ifndef
#ifdef
#elif
#endif
#error 在编译期间产生错误
#warning 在编译期间产生警告
#pragma
#pragma GCC dependency 用于监控文件,防止所依赖的文件,修改后而不知道。
#pragma GCC poison 用于禁用某些标识符
#pragma pack(n) 设置结构、联合的补齐和对齐字节数
n的值必须比默认值的要小
对齐边界必须是 2 的较小次方
五、库
库就目标文件的集合,我们把不需要升级更新维护的代码打包合并在一起方便使用,也可以对源代码进行保密。

静态库在使用时是把被调用的代码复制到调用模块中,然后在执行程序时,静态库就不需要了。

静态库的执行速度快,但占用空间大,当库中的内容发生变化时,需要重新编译出新的程序,因此不能轻易修改库中的内容。

而共享库只是在调用模块中嵌入调用代码的在库的相对位置的地址,当执行程序时,共享库会的程序一起加载到内存中,当执行到调用共享库中代码的指令时跳转到共享中执行,执行完毕后在跳转回来。

占用空间小,方便更将新(共享库发生变化后,程序不需要再次编译),相对于静态库执行效率略低。

静态库的扩展名为.a,共享库(动态库)的扩展名为.so。

六、静态库
1、创建静态库
编写源代码:vi .c/.h
编译源代码:gcc -c xxx.c -> xxx.o
打包生成静态库:ar -r libxxx.a x1.o x2.o …
ar命令的一些参数:
-r 把目标文件添加到静态库中,已经存在的更新
-q 将目标文件追加到静态库的末尾
-d 从静态库中删除目标文件
-t 显示静态库中有哪些目标文件
-x 把静态库拆分成目标文件
2、调用静态库
直接调用:调用者要和库在同一路径下
gcc main.c libxxx.a
设置环境变量:设置方法与C_INCLUDE_PATH类似
1.打开 vim ~/.bashrc 文件
2.在文件末尾添加一行
export LIBRARY_PATH=$LIBRARY_PATH:库文件的路径
3.重新加载配置文件 source ~/.bashrc
4.编译时要指定库名
gcc main.c -lmath
设置编译参数:-L路径
gcc main.c -L路径 -lmath
3、运行
在编译时已经把被函数的二进制复制到可执行文件中了,在执行时不再需要静态库文件。

七、共享库
1、创建共享库
编写源代码:vi .c/.h
编译出位置无关目标文件:
gcc -c -fpic xxx.c -> xxx.o
链接生成共享库:
gcc -shared x1.o x2.o x3.0 … -o libxxx.so
2、调用共享库
直接调用:调用者要和库在同一路径下
gcc main.c libxxx.so
设置环境变量:设置方法与C_INCLUDE_PATH类似
1.打开 vim ~/.bashrc 文件
2.在文件末尾添加一行
export LIBRARY_PATH=$LIBRARY_PATH:库文件的路径
3.重新加载配置文件 source ~/.bashrc
4.编译时要指定库名
gcc main.c -lmath
设置编译参数:-L路径
gcc main.c -L路径 -lmath
3、运行
在使用共享库时,调用者只是记录了被代码在库的位置,因此在执行时需要共享库同时被加载。
操作系统会根据LD_LIBRARY_PATH环境变量的设置来加载共享库。

八、动态加载共享库
#include <dlfcn.h>
1、加载共享库
void *dlopen(const char *filename, int flag);
filename:共享库的库名,或路径
flag:
RTLD_LAZY 使用时才加载
RTLD_NOW 立即加载
返回值:共享库的句柄(类似文件指针)

2、获取标识符地址并使用
void *dlsym(void *handle, const char *symbol);
handle:共享库的句柄
symbol:标识符的名字
返回值:标识符在共享库中的位置(地址,可以解引用,或跳转过去)。
3、卸载共享库
int dlclose(void *handle);
handle:共享库的句柄
返回值:成功返回0,失败返回-1

4、获取错误信息
char *dlerror(void);
返回值:会把在使用共享库的过程中出现的错误,以字符串形式返回

九、辅助工具
nm:查看目标文件、可执行文件、静态库、共享库的中的符号列表
ldd:查看可执行程序所依赖的共享库有哪些
strip:减肥,去除掉目标文件、可执行文件、静态库和共享库中的符号列表、调试信息。
objdump 显示二进制模块的反汇编信息
一、错误处理
1、通过函数返回值表示错误
返回值合法表示成功,非法表示失败
返回有效指针表示成功,空指针(NULL/0xffffffff)表示失败
返回0表示成功,-1表示失败
永远成功,printf
练习1:str_len 求字符串长度,若指针为空则报错。
练习2:str_cpy(char* dest,size_t dlen,char* src) 字符串拷贝函数,考虑目标的溢出问题,如果目标位置无效或超出则报销。
练习3:intmin 求两个整数的最小值,二者相等,则报错。
练习4:intagv 求两个整数的平均值,该函数永远成功。

2、通过errno表示错误
	errno是一个全书变量,它的声明在errno.h文件中,它的值随时可能发生变化。
	
	可以将它转换成有意义的字符串,strerror(errno) <=> perror("msg")
	注意:在函数执行成功的情况下,不会修改errno的值。
		因此不能以errno的值不等于0就判断函数执行出错了。
		所以通常会和函数的返回值配合,通过返回值判断是否出错,而通过perror查询出了什么类型的错误。

二、环境变量
以字符串形式存在的,绝大多数据记录的是路径信息,它表示了当前操作系统的资源配置,环境设置等相关信息。
1、环境变量表
每个程序运行,时操作系统都会把所有的环境变量记录到一张表中,传给程序。
通过main函数参数获取 int main(int argc,char* argv[],char* environ[])
通过声明为全局变量获取 extern char** environ;

2、环境变量函数
char *getenv(const char *name);
功能:根据环境变量名,获取环境变量的值

int putenv(char *string);
功能:以name=value形式设置环境变量,如果name存在则更新,不存在则添加。
返回值:成功返回0,失败返回-1

int setenv(const char *name, const char *value, int overwrite);
功能:设置name环境变量的值为value,如果name存在且overwrite不为零则更新,否则不变。
int unsetenv(const char *name);
功能:从环境变量表中删除name

int clearenv(void);
功能:清空环境变量表

注意:操作系统记录的环境变量的数据记录一块特殊的存储空间,而在程序自己添加的环境变量需要自己准备存储空间。
注意:对于环境变量的修改,只能影响自己,不能影响别人。

练习5、从文件中读取一个程序的配置信息
ServerIP = 192.168.0.1
Port = 8899
MaxSize = 100
ContinueSec = 3
LogPath = /zhizhen/temp/
DataDath = /zhizhen/data/

练习6、给LIBRARY_PATH添加一个路径(/home/zhizhen/lib)。
LIBRARY_PATH=/home/zhizhen
LIBRARY_PATH=/home/zhizhen:/home/zhizhen/lib

三、内存管理
自动分配/释放内存auto_ptr STL 调用标准C++中的new/delete
new/delete 构造/析构 C++ 标准C中的malloc/free
malloc/free 标准C 调用POSIX
brk/sbrk POSIX 调用Linux系统接口
mmap/munmap Linux 调用内核接口
kmalloc/vmalloc 内核 调用驱动
get_free_page 驱动 …

四、进程映像
程序是保存在磁盘上的可执行文件,加载到内存中被操作系统调用执行的程序叫进程(一个程序可以被同时执行多次形成身份不同的进程)。
进制在内存空间中的分布情况叫进制映像,从低地址到高地址依次排列的是:
代码段/只读段:
二进制指令、字符串字面值、具有const属性且被初始化过的全局、静态变量。
数据段:被初始化过的全局变量和静态变量。
BSS段:没有初始化过的全局变量和静态变量,进程一旦加载成功就会把这段内存清理为零。
堆:动态的分配、管理,需要程序员手动操作。
栈:非静态的区部变量,包括函数的参数、返回值。
从高地址向低地址使用,和堆内存之间存在一段空隙。
命令行参数及环境变量表:命令行参数、环境变量

练习7:在一个程序中打印各段内存的一个地址,然后与操作系统的中内存分配情况表比较,然后一一对应内存的分配情况。
	getpid() 可以获取进程的编号
	cat /proc/xxxx/maps
	size 程序名 查看text data bss各段的大小

五、虚拟内存
每个进程都有各自独立的4G字节的虚拟地址空间,我们在编程时使用的永远这都是这4G的虚拟地址空间中的地址,永远无法直接访问物理地址。

操作系统不让程序直接访问物理内存而只能使用虚拟地址空间一方面为了操作系统自身的安全,另一方面可以让程序使用到比物理内存更大的地址空间(把硬盘上的特殊文件与虚拟地址空间进行映射)。

4G的虚拟地址空间被分为两个部分:
	0~3G 	用户空间
	3G~4G	内核空间 
	注意:用户空间的代码不能直接访问内核空间的代码和数据,但可以通过系统调用(不是函数,但以函数形式调用)进入到内核态间接与内核交换数据。
	
如果使用了没有映射过的或者访问没有权限的虚拟内存地址,就会产生段错误(非法内存访问)。

一个进程对应一个用户空间,进程一旦切换,用户空间也会发生变化,但内核空间由操作系统管理,它不会随着进程的切换而变化,内核空间由内核所管理的一张独立且唯一的init_mm表进行内存映射,而用户空间的表是每个进程一张。

注意:每个进程的内存空间完全独立,不同的进程间交换虚拟内存地址没有任何意义,所以进程之间不能直接进行通信,需要由内核中转、协调。

虚拟内存到物理内存的映射以页为单位(一页等于4K=4096字节)。

六、内存管理API
#include <unistd.h>
void *sbrk(intptr_t increment);
increment:
0 获取未分配前的内存首地址(也就是已经分配尾地址)
>0 增加内存空间
<0 释放内存空间
返回值:未分配前的内存首地址,以字节为单位。

int brk(void *addr);	
功能:设置未分配内存的首地址
返回值:成功返回0,失败返回-1

它们背后维护着一个指针,该指针记录的是末分配的内存的首地址(当前堆内存的最后一个字节的下一个位置)。
它们都可以进行映射内存的取消映射的功能(系统级的内存管理),但为了方便起见,sbrk一般用于分配内存,brk用于释放内存。
注意:sbrk/brk分配的释放的都是使用权,直正的映射工作由其它系统调用完成(mmap/munmap)。

#include <sys/mman.h>
void* mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
功能:把虚拟内存地址与物理内存或文件建立映射关系。
addr:要映射的虚拟内存地址,如果为NULL操作系统会自动选择一个虚拟地址与物理内存映射。
length:要映射的字节数
prot:权限
flags:映射标志
fd:文件描述符(与内存映射没有关系)
offset:文件映射偏移值
返回值:映射成功后的虚拟内存地址,如果出错返回值为0xffffffff

int munmap(void *addr, size_t length);
功能:取消映射
addr:需要取消映射的内存首地址
length:需要映射的字节数
返回值:成功返回0,失败返回-1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值