Kernel 学习
Linux Kernel
C 相关知识
链接(Link)
C语言代码经过编译以后,并没有生成最终的可执行文件(.exe 文件),而是生成了一种叫做目标文件(Object File)的中间文件(或者说临时文件)。目标文件也是二进制形式的,它和可执行文件的格式是一样的
对于 Visual C++,目标文件的后缀是.obj
;对于
GCC,目标文件的后缀是.o
目标文件并不能直接执行,首先需要载入到链接器中,链接器确认main函数为初始进入点,把符号引用绑定到内存地址,把所有的目标文件集中在一起,在加上库文件,从而产生可执行文件
静态链接
函数库的一份拷贝是可执行文件的物理组成部分
静态链接的模块被链接-编辑并载入以便运行
动态链接
可执行文件只是包含了文件名,载入器在运行时寻找程序所需的函数库
动态链接的模块被链接-编辑后载入,并在运行时链接以便运行
回调函数
回调函数的好处和作用就是解耦
来自维基百科的对回调(Callback)的解析:In computer programming, a callback is any executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at a given time. This execution may be immediate as in a synchronous callback, or it might happen at a later time as in an asynchronous callback. 也就是说,把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调。如果代码立即被执行就称为同步回调,如果在之后晚点的某个时间再执行,则称之为异步回调。
来自Stack Overflow某位大神简洁明了的表述:A "callback" is any function that is called by another function which takes the first function as a parameter。 也就是说,函数 F1 调用函数 F2 的时候,函数 F1 通过参数给 函数 F2 传递了另外一个函数 F3 的指针,在函数 F2 执行的过程中,函数F2 调用了函数 F3,这个动作就叫做回调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。
1 |
|
只要我们改变传进库函数的参数,就可以实现不同的功能,这样是很灵活的,并且丝毫不需要修改库函数的实现,这就是解耦。
带参的回调函数
示例:
1 |
|
可以看出是通过增加一个参数来保存回调函数的参数值。
预处理指令
常见的预处理指令:
#define
宏定义#undef
未定义宏#include
文本包含#ifdef
如果宏被定义就进行编译#ifndef
如果宏未被定义就进行编译#endif
结束编译块的控制#if
表达式非零就对代码进行编译#else
作为其他预处理的剩余选项进行编译#elif
这是一种#else和#if的组合选项#line
改变当前的行数和文件名称#error
输出一个错误信息#pragma
为编译程序提供非常规的控制流信息
#include
对于#include <io.h>
,编译器从标准库路径开始搜索
对于#include "io.h"
,编译器从用户的工作路径开始搜索
#error
1 |
|
#progma
#progma
主要功能是为编译程序提供非常规的控制流信息
预定义标识符
为了处理一些有用的信息,预处理定义了一些预处理标识符,虽然各种编译器的预处理标识符不尽相同,但是他们都会处理下面这4种:
1 |
|
POSIX标准
POSIX,Portable Operating System Interface,是UNIX系统的一个设计标准,很多类UNIX系统也在支持兼容这个标准,如Linux
POSIX是IEEE为要在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称
Linux基本上逐步实现了POSIX兼容,但并没有参加正式的POSIX认证
当前的POSIX主要分为四个部分:Base Definitions、System Interfaces、Shell and Utilities和Rationale
POSIX大约有100个过程调用,其中最主要的过程调用如下被分为四类:
- 进程管理
pid = fork
:创建与父进程相同的子进程pid = waitpid(pid, &statloc, options)
:等待一个子进程终止s = execve(name, argv, environp)
:替换一个进程的核心映像exit(status)
:终止进程执行并返回状态
- 文件管理
fd = open(file, how, ...)
:打开一个文件供读、写或两者s = close(fd)
:关闭一个打开的文件n = read(fd, buffer, nbytes)
:把数据从一个文件读到缓冲区中n = write(fd, buffer, nbytes)
:把数据从缓冲区写到一个文件中position = lseek(fd, offset, whence)
:移动文件指针s = stat(name, &buf)
:取得文件的状态信息
- 目录和文件系统管理
s = mkdir(name, mode)
:创建一个新目录s = rmdir(name)
:删除一个空目录s = link(name1, name2)
:创建一个新目录项name2,并指向name1s = unlink(name)
:删去一个目录项s = mount(special, name, flag)
:安装一个文件系统s = umount(special)
:卸载一个文件系统
- 杂项
s = chdir(dirname)
:改变工作目录s = chmod(name, mode)
:修改一个文件的保护位s = kill(pid, signal)
:发送信号给一个进程seconds = time(&seconds)
:自1970年1月1日起的流逝时间
头文件
- unistd.h
- Linux/Unix系统内置的头文件,包含了许多系统服务的函数原型,例如read函数、write函数和getpid函数等
- 其作用相当于windows操作系统的"windows.h",是操作系统为用户提供的统一API接口,方便调用系统提供的一些服务
Makefile Grammar
Makefile文件里描述的是编译的时候依赖关系,宏定义,编译参数,链接生成的程序名字等等
文件包含
- include 文件名
- 将其他makefile文件包含进来,形成更大的makefile文件
变量定义
- 变量名 := 变量值
- 变量定义
- 变量名 += 变量值1 [变量值2] [...]
- 追加新的变量值
- 自动变量
- 自动变量的值会依据规则中的target 和 prerequisites自动计算其值,以$开始
- **$@** 为规则中的target名称
- $< 为规则中第一个prerequisite名称
内置命令
字符串处理函数:subst、patsubst、strip、findstring、filter、filter-out、sort、word、wordlist、words、firstword、lastword
文件名处理函数:dir、notdir、suffix、basename、addsuffix、addprefix、join、wildcard、realpath、abspath
dir
$(dir name1 [name2] […])
获取name中文件对应的路径
E.g.
1
$(dir src/foo.c hacks) # 获取src/ ./两个路径
nodir
$(dir name1 [name2] […])
获取name中除去路径的信息
E.g.
1
$(dir src/foo.c hacks) # 获取foo.c hacks两个文件信息
basename
$(basename name1 [name2] […])
获取names中除去后缀信息
E.g.
1
$(basename src/foo.c src/bar.b hacks.c) # 获得信息src/foo src/bar hacks
addsuffix
- $(addsuffix suffix,name1 [name2] […])
wildcard
$(wildcard pattern)
pattern为匹配的模式
E.g.
1
$(wildcard %.c) 查找当前目录下文件名以.c结尾的文件
条件循环函数:if
循环处理函数:foreach
foreach
$(foreach var, list, text)
每循环一次var从list中按顺序取值一个
E.g.
1
2dirs := C_DIR S_DIR
file := $(foreach dir,$(dirs),$(wildcard $(dir)/*)) # 将C_DIR和S_DIR文件夹下面的所有文件添加到file变量中
源码编译
源码的安装一般由3个步骤组成:配置(configure)、编译(make)、安装(make install)、卸载(make uninstall)
configure
configure
通常检查环境,配置编译条件,一般是一个可执行脚本,有很多选项,在待安装的源码路径下使用命令./configure
–help输出详细的选项列表。一般用来生成
Makefile,为下一步的编译做准备。
./configure --prefix
它的作用是配置安装路径,如:
./configure --prefix=/usr
,意思是将该软件安装在/usr下面,执行文件就会安装在/usr/bin,资源文件就会安装在/usr/share,--prefix选项还有一个好处:卸载的时候,直接删除一个文件夹(安装目录)即可;移植软件也只需拷贝整个目录到另外一个机器即可(相同的操作系统)。./configure
不配置--prefix选项,安装后可执行文件默认放在/usr/local/bin,库文件默认放在/usr/local/lib,配置文件默认放在/usr/local/etc,其它的资源文件放在/usr/local/share
make
make
命令从Makefile中读取指令,然后进行编译。
大多数的源代码包都经过这一步进行编译(当然有些perl或python编写的软件需要调用perl或python来进行编译)。如果在make过程中出现error,你就要记下错误代码(注意不仅仅是最后一行),然后你可以向开发者提交,bugreport(一般install里有提交地址),或者你的系统少了一些依赖库等,这些需要自己仔细研究错误代码。
make install
make install
指令用来安装,从Makefile中读取指令,安装到指定的位置。安装的时候一般都需要root权限。
在make install
之前有些软件需要先运行make check
或make test
来进行一些测试。
make uninstall
make uninstall
指令用来卸载,在原先make的目录下运行,但前提是makefile文件指定过uninstall,如果没有指定则需要手动删除。
其他
当我们在使用make命令时,常常会在make后面加上其他单词,比如check,install,installcheck…这些单词都是make的参数,我们称之为“目标(targets)”。
最常见的几个target:
- make all:编译程序、库、文档等(等同于make)
- make install:安装已经编译好的程序。复制文件树中到文件到指定的位置
- make unistall:卸载已经安装的程序。
- make clean:删除由make命令产生的文件
- make distclean:同时删除./configure和make产生的临时文件
- make check/make test:测试刚刚编译的软件(某些程序可能不支持)
- make installcheck:检查安装的库和程序(某些程序可能不支持)
- make dist:重新打包成packname-version.tar.gz
GCC
几种常见的gcc参数
gcc -I /home/hello/include
- 表示将/home/hello/include作为第一个寻找头文件的目录
- 寻找顺序依次是:/home/hello/include --> /usr/include --> /usr/local/include
gcc -L /home/hello/lib
- 表示将/home/hello/lib作为第一个寻找库文件的目录
- 寻找顺序依次是:/home/hello/lib --> /usr/lib --> /usr/local/lib
gcc -lworld
- 在上面的lib的路径中寻找libworld.so动态库文件,如果gcc编译选项中加入了“-static”表示寻找libworld.a静态库文件,程序链接的库名是world
gcc -shared hello.c -o libhello.so
- 将hello.c编译成动态链接库
gcc -E hello.c -o test.i
- 将test.c预处理输出test.i文件
gcc -S test.i
- 将预处理输出文件test.i汇编成test.s文件
gcc -c hello.c
- 仅执行编译操作,不进行连接操作。将hello.c编译成hello.o
gcc hello.c
- 将hello.c编译成一个a.out的可执行文件
gcc hello.c -o hello
- 将hello.c编译出一个名为test的可执行文件,-o用作指定输出文件的文件名
gcc hello.c -O2
- 默认进行O0优化,-O1进行O1优化,-O2进行O2优化,-O3进行O3优化
常见的错误
xxxx.h: No such file or directory
:引用出错,使用参数-I
-I
参数可以用相对路径,比如头文件在当前目录,可以用-I.
来指定
undefined reference to 'xxxxx'
:链接错误,应该指定链接程序要用到的库,使用参数-l
指定/usr/bin/ld: cannot find -lxxx
:表明链接程序ld在那3个库文件目录里找不到libxxx.so,使用参数-L
指定- 大部分libxxxx.so只是一个链接,以RH9为例,比如libm.so它链接到/lib/libm.so.x,/lib/libm.so.6又链接到/lib/libm-2.3.2.so,如果没有这样的链接,还是会出错,因为ld只会找libxxxx.so,所以如果你要用到xxxx库,而只有libxxxx.so.x或者libxxxx-x.x.x.so,做一个链接就可以了
ln -s libxxxx-x.x.x.so libxxxx.so
- 大部分libxxxx.so只是一个链接,以RH9为例,比如libm.so它链接到/lib/libm.so.x,/lib/libm.so.6又链接到/lib/libm-2.3.2.so,如果没有这样的链接,还是会出错,因为ld只会找libxxxx.so,所以如果你要用到xxxx库,而只有libxxxx.so.x或者libxxxx-x.x.x.so,做一个链接就可以了
-pthread & -lpthread
一般情况下我们在链接一个(文件名为libxxx.so或libxxx.a等)库时,会使用-lxxx
的方式;在Linux中要用到多线程时,需要链接pthread库,按照惯例,我们应该使用-lpthread
的方式来进行链接;但很多开源代码都是使用了-pthread
参数,而非使用-lpthread
,这是因为:
- 为了可移植性:在Linux中,pthread是作为一个单独的库存在的(libpthread.so),但是在其他Unix变种中却不一定,比如在FreeBSD中是没有单独的pthread库的,因此在FreeBSD中不能使用
-lpthread
来链接pthread,而使用-pthread
则不会出现这个问题,因为FreeBSD的编译器能正确地将-pthread
展开为该系统下的依赖参数。同样的,其他不同的变种也会存在差异。为了保持较高的可移植性,我们最好使用-pthread
,即使它目前不是C标准,但基本上是事实标准 - 添加额外的标志:在多数系统中,
-pthread
会被展开为-D_REENTRANT -lpthread
,即除了链接pthread库外,还先定义了宏_REENTRANT。定义这个宏的目的,是为了打开系统头文件中的各种多线程支持分支。比如我们常常使用的错误码标志errno,如果没有定义_REENTRANT,则实现为一个全局变量;反之则会为每个线程所独有,从而避免线程竞争错误
目前gcc 4.5.2中已经没有了关于
-lpthread的介绍了。所以以后的多线程编译应该用-pthread
,而不是-lpthread
。
Linux 操作
下载文件 wget
- wget -O-
- -O 会把url中获取的数据统一写入 '-O' 指定的file中,这里是写到 '-' 中,即打印到标准输出,通常为控制台,进而可以通过管道传输给其他命令
添加环境变量
暂时添加
1 |
|
为当前用户永久添加环境变量
1 |
|
退出 vim 后需要 source ~/.bashrc
,让环境立即生效。
为所有用户永久添加某一环境变量
1 |
|
退出 vim 后需要
source /etc/profile
,让环境立即生效。
/etc/environment 下面添加
1 |
|
退出 vim 后需要
source /etc/environment
,让环境立即生效。