Linux系统内核笔记

一、课程介绍
UNIX/Linux环境C语言,借助学习操作系统的接口的方法来学习、理解操作系统的
运行机制以及一些网络协议
C/C++、数据结构和算法 与平台无关,重点是算法逻辑
Uinx/Linux/Android/IOS 平台相关,系统接口
嵌入式/驱动/移植 硬件相关,硬件接口

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

二、UNIX操作系统
丹尼斯.里奇、肯.汤姆逊于1971年左右在美国贝尔实验室,使用C语言开发了这款操作
系统。
系统的特点是多用户、多任务,支持多种处理器架构高安全性、高可靠性、高稳定性。

	既可以构建大型关键业务系统的商用服务器,也可以构建面向移动终端的、手持设备
等相关的嵌入式应用。
	
	三大衍生版本
		System V:银行、电信在使用的服务器系统
		Berkley:MacOS iOS带界面的
		Hybrid:Minix、Linux

三、Linux操作系统
类Uinx系统,免费开源,它指的是系统的内核,凡是使用这种内核的操作系统都叫作
Linux系统(发行板),严格意义上讲Linux指的是内核,隶属于GNU工程。
手机、平板电脑、路由器、视频游戏控制平台、PC、大型计算机、超级计算机。
标志是一只企鹅,因为企鹅是南极的标志性动物,根据国际公约南极为全人类共同所
有,所以Linux使用企鹅作为标志,也表明:开源的Linux为全人类共用所有,任何公司或
个人无权将其私有

	Minix操作系统是一个微型的类UNIX系统、免费开源,而Linux之父就是在参照这款操作
,才写出第一个版本的Linux内核代码
	GNU工程:是自由软件基金会所创立的一个开源组织,基本原则就是共享,主旨是发展出
一个有别于商业UNIX的免费且完整的类UNIX系统--GNU Not UNIX。目前Linux内核由它进行维
护,所以Linux也叫GNU Linux
	GPL通用公共许可证:允许对某些成果及派生成果重用、修改、复制,对所有人都是自
由的,但不能声明做了原始工作,或声明由他人所作。
	POXIX标准:统一的系统编程接口规范,它规定了操作系统以接口形式提供的功能的名字
、参数、返回值,它保障了应用程序源码级的可移植性,而Linux完全遵循了这个标准
版本管理:
	早期版本:0.01、0.02、.....、0.09、1.0
	旧计划:A.B.C
		A 主版本号
		B 次版本号
		C 补丁序号
	新计划:A.B.C.D.E
		D 构建次数
		E 描述信息

特点:
	多用户、多任务
	遵循GNU/GPL具有开放性
	设备独立性
	丰富的网络功能
	可靠的系统安全
	良好的可移植性
发行板:
	Ubuntu
	Fedora
	Debian
	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_INTCLUDE_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=$LIBRAY_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.o … -o libxxx.so
2、调用共享库
直接调用:调用者要和库在同一路径下
gcc main.c libxxx.so
设置环境变量:设置方法于C_INCLUDE_PATH类似
1、打开 vim ~/.bashrc 文件
2、在文件末尾添加一行
export LIBRARY_PATH=$LIBRAY_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:
		RYLD_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 -S 显示二进制模块的反汇编信息

/***内存管理/

一、错误处理
1、通过函数返回值表示错误
返回值合法表示成功,非法表示失败
返回有效指针表示成功,空指针(NULL/0xffffffff)表示失败
返回0表示成功,-1表示失败
永远成功

练习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形式设置环境变量,如果环境变量存在则更新,不存在则添加
			成功返回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);
		功能:清空环境变量表
		操作系统记录的环境变量的数据记录一块特殊的存储空间,而在程序自己添加的
	环境变量需要自己准备存储空间
		注意:对于环境变量的修改,只能影响自己,不能影响别人

	练习五:从文件中读取一个程序的配置信息
		ServerIP = 192.168.0.1
		Port = 8899
		MaxSize = 100
		ContinueSec = 3
		LogPath = /zhizhen/temp
		DataDath = /zhizhen/data/
	练习六:给LIBRARY_PATH添加一个路径(/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段:	没有被初始化过的全局变量和静态变量,进程一但加载成功就会把这段内存
			清理为零
	堆:	动态的分配、管理,需要程序员手动操作
	栈:	非静态的局部变量,包括函数的参数、返回值
			从高地址项低地址使用和堆内存之间存在一段空隙,
	命令行参数及环境变量表:命令行参数、环境变量
	
	练习1:在一个程序中打印各段内存一个地址,然后于操作系统中内存分配情况表
			然后一一对应内存的分配情况
			getpid()	可以获取进程的编号
			cat/proc/xxx/maps
			size 程序名	查看text data bss各段的大小

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

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

	4G的虚拟地址空间被分为两个部分:
		0~3G	用户空间
		3G~4G	内核空间
		注意:用户空间的代码不能直接访问内核空间的代码和数据,但可以通过系统
	调用(不是函数,但以函数形式调用)进入到内核空间间接与内核交换数据

	如果使用了没有映射过的虚拟内存地址,或者访问没有权限的虚拟内存地址,就会
产生段错误(非法内存访问)
	
	一个进程对应一个用户空间,进程一但切换,用户空间也会发生变化,内核空间有
操作系统管理,它不会随着进程的切换而发生变化,内核空间由内核所管理的一张独立
且唯一的init mm表进行内存映射,而用户空间的表是每个进程一张

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

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

六、内存管理API
他们可以进行映射内存的取消映射(系统级的内存管理)

	void *sbrk(intptr_t increment);
	返回值:未分配前的内存首地址,以字节为单位
	increment:
			0	获取未分配前的内存首地址(也就是已经分配尾地址)
			>0	增加内存空间
			<0	释放内存空间
			
	int brk(void* addr);
	功能:设置未分配内存的首地址
	返回值:成功返回0,失败返回-1
		
	他们可以进行映射内存的取消映射(系统级的内存管理),他们背后维护着一个指针,
该指针记录的是未分配的内存的首地址(当前堆内存的最后一个字节的下一个位置)。
	他们可以进行映射内存的取消映射(系统级的内存管理),但为了方便起见,sbrk一
般用于分配内存,brk用于释放内存。
	注意:sbrk/brk分配的释放的都是使用权,真正的映射工作由其他系统调用完成(mmap/
munmap)
	
	练习1:计算1000以内的素数,存在到堆内存中,不要浪费内存
	练习2:使用sbrk和brk实现顺序栈
	
	#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

/Linux文件操作**/

一、文件描述符
UNIX/Linux系统绝大部分功能都是通过系统调用实现,比如:open/close…
UNIX/Linux把系统调用都封装成了C函数的形式,但它们并不是标准C的一部分
标准库中的函数绝大部分时间都工作在用户态,但部分时间也需要切换到内核
(进行了系统调用),比如:fread/fwrite/malloc/free
我们自己所编写的代码也可以直接调用系统接口进入内核态(进行系统调用),
比如:brk/sbrk/mmap/munmap

	系统调用的功能代码存在于内存中,接口定义在C库中,该接口通过系统中断实现
调用,而不是普通函数进行跳转
	注意:从用户态切换到内核态或从内核态返回到用户态都会消耗时间
	time a.out
	real	0m0.137s	总执行时间 = 用户态 + 内核态 + 切换消耗
	user	0m0.092s	用户态执行时间
	sys		0m0.040s	内核态执行时间
	
	strace 程序 可以跟踪系统调用

二、一切皆文件
在UNIX/Linux系统下,几乎所有资源都是有文件形式提供了,所以在UNIX/Linux系统
下一切皆文件,把操作系统的服务、设备抽像成简单的文件,提供一套简单同一的接口,
这样程序就可以像访问磁盘上的文件一样访问串口、终端、打印机、网络等功能。
大多数情况下只需要 open/read/write/ioctl/close 就可以实现对各种设备的输入、
输出、设备、控制等。
UNIX/Linux下几乎任何对象都可以当作特殊类型的文件,可以以文件的形式访问
目标文件 里面记录的是一些文件信息,相关条目
设备文件 在系统的/dev目录下存储了所有的设备文件
stderr
stdin
stdout
普通文件
链接文件
管道文件
socket文件

三、文件相关系统调用
open 打开或创建文件
creat 创建文件
close 关闭文件
read 读文件
write 写文件
lseek 设置文件读写位置
unlink 删除链接
remove 删除文件

四、文件描述符
文件描述符是一个非负整数,表示一个打开的文件,由系统调用open/creat/socket返回值
为什么使用文件描述符而不像标准库使用文件指针?
因为记录文件相关信息的结构存储在内核中,为了不暴露内存的地址,因此文件结构指针
不能直接给用户操作,内核中记录一张表,其中一列是文件描述符对应一列文件结构指针,
文件描述符就相当于获取文件结构指针的下标,
内核中已经由三个已经打开的文件描述符,它们的宏定义在unistd.h:
stdin 0 STDIN_FILENO
stdout 1 STDOUT_FILENO
stderr 2 STDERR_FILENO

五、open/creat/close
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
功能:打开文件
pathname:文件的路径
flags:打开的权限
	O_RDONLY		只读
	O_WRONLY		只写
	O_RDWR			读写
	O_CREAT			文件不存在则创建
	O_EXCL			如果文件存在,则创建失败
	O_NOCTTY		当打开的是终端设备文件,不要把该文件当做主控终端
	O_TRUNC			清空
	O_APPEND		追加

返回值

int open(const char *pathname, int flags, mode_t mode);
功能:创建文件
pathname
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值