Linux Kernel Development 3rd Edition 读书笔记(1)
该书有第二版中文版,第三版还没看到,这里记录下该书的要点,并翻译成中文以加深印象.欢迎指正第一章 Introduction To The Linux Kernel1. Linux内核类型LinuxLinux是单一的内核(monolithic kernel),就是说,Linux内核在单一的地址空间中运行,但是Linux借鉴了很多微内核(mircokernel)的优点. Linux使用了
第一章 Introduction To The Linux Kernel
1. Linux内核类型Linux
Linux是单一的内核(monolithic kernel),就是说,Linux内核在单一的地址空间中运行,但是Linux借鉴了很多微内核(mircokernel)的优点. Linux使用了模块化设计,内核抢占,支持内核线程和动态加载不同的内核模块到内核映像空间.相反,Linux没有微内核设计的性能较差的缺点:一切都运行在内核模式,直接函数调用,没有消息传递.尽管如此,Linux是模块化,线程支持及内核自我调度的内核.
2. Linux与Unix的区别
(1) Linux支持动态加载内核模块,尽管Linux内核是单一型内核,它可以根据命令加载或者卸载模块.
(2) Linux支持对称多处理器架构(SMP),尽管多数商业Unix系统支持SMP,但传统的Unix 实现多数不支持.
(3) Linux内核是可抢占式的,不像传统的Unix内核,Linux可以抢占在内核运行的任务.商业Unix系统中Solaris and IRIX支持内核抢占,但多数Unix内核是非抢占式的.
(4) Linux的线程支持比较有趣,正常的进程和线程之间并没有什么区别,对于内核来说,所有的进程都一样,仅仅会有不同的共享资源.
(5) Linux提供面向对象的设备模型,该设备模型设备类,即插即用事件和用户空间的设备文件系统(sysfs).
(6) Linux忽略了Unix的一些内核开发者任务实现糟糕的通用特性,比如STREAMS,或者无法容易实现的标准.
(7) Linux的一切都是免费的.这样Linux的开发都是自由的,
第二章 Getting Started with the Kernel
1. git使用
使用GIt可以获得最近的linux版本:
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
check out完成后,可以使用如下命令:
$ git pull
来更新Linux内核到最新的版本
2. The Kernel Source Tree
3. 配置内核
(1) 配置选择有Boolean和tristates两种类型.Boolean类型可以选择yes或no.内核特性,比如 CONFIG_PREEMPT通常就是Boolean型.tristates类型有yes,no,module3个值可选.设置成module表示编译成模块(可动态加载的对象).tristates类型的数据中,yes表示将代码编译到内核映像中,而不是一个模块.驱动通常就是tristates类型来配置的.
(2) 配置内核命令
文本模式下: $ make config
该命令会一个选项一个选项的让用户选择,会花很多时间.因此最好使用图形化的配置.
$ make menuconfig 或者基于gtk+的工具: $ make gconfig
$ make defconfig 该命令创建一个基于默认配置的配置.
$ make oldconfig 当更新了配置,或者使用了配置文件到新的内核代码中,使用该命令进行验证并更新配置.在编译内核之前需要运行该命令.
配置选项CONFIG_IKCONFIG_PROC能够将配置文件保存并压缩到 /proc/config.gz中.可以使用如下命令将备份的配置文件恢复进行新的编译.
$ zcat /proc/config.gz > .config
$ make oldconfig
内核配置完成后,使用make命令就可以编译内核了.
$ make
如果不想看到过多的编译信息,而只想看到警告和错误,可以使用如下命令重定向到其他文件:
$ make > ../detritus
还可以使用如下命令将无价值的输出信息重定向到null,也就是丢弃.
$ make > /dev/null
4.安装新内核
这取决于系统架构和Bootloader, 如果是x86使用grub引导,只需复制arch/i386/boot/bzImage目录到 /boot,更名为 vmlinuz-version之类的名字,然后编辑 /boot/grub/grub.conf,为新内核添加一个新的entry.对于Lilo,则编辑/etc/lilo.conf然后重新运行Lilo.
使用% make modules_install命令来安装内核模块,该命令将编译好的模块放到 /lib/modules下.
5.内核区别与用户程序的特点
(1) 内核不能访问C库和标准头文件
常用的C库函数有在内核中的实现,如printk()代替printf(),用法基本一样,唯一的区别是printk可以添加一个表示信息优先级的宏(注意没有逗号),如:
printk(KERN_ERR "this is an error!\n");
(2) 内核由GNU C编写
支持内联函数;内嵌汇编;likely()和unlikely()来优化分支语句.
(3) 内核缺少像用户空间一样的内存保护机制
(4) 内核很难进行浮点运算
(5) 内核的每个进程都只有固定的很小的栈空间
x86架构上一般为4KB或者8KB.32bit系统8KB,64bit系统16KB.
(6) 因为内核有异步中断,可抢占,支持SMP,同步和并发运行是内核需要重点注意的方面
通常的解决办法是使用自旋锁和信号量.
(7) 可移植性非常重要
第三章. Process Management
(1)进程(Process)是程序(存储在存储介质上的对象代码)在执行中间的状态.不仅包括执行的代码,还包括打开的文件,等待的信号,内核数据,处理器状态,内存空间,一个或多个执行的线程,包含全局变量的数据区.对于Linux来说,线程是一种特殊种类的进程.
(2)Linux使用fork()系统调用来创建进程,复制一个和现在相同的进程.创建该进程的进程叫父进程,被创建的是子进程.父进程进行执行,子进程从fork()返回的位置开始执行.fork()从内核返回两次,一次是父进程,一次是刚创建的子进程.exec()系统调用用来创建新的地址空间,加载新的程序.在现代Linux中,fork()通常实现为clone().exit()系统调用用来结束进程,释放资源.父进程可以调用wait4()来获取终结的子进程的状态.当进程存在时,会被置为僵尸状态直到父进程调用wait()或者waitpid().
(3)内核将进程列表保存在一个双向循环链表中,叫做task list.每个元素是task_struct结构的进程描述符.定义在<linux/sched.h>中.task_struct很大,在32位系统中有1.7K字节.进程描述符保存了执行程序的信息,如打开的文件,进程地址空间,等待信号,进程状态等.
(4)thread_info定义在stack底部(向下增长)或者stack顶部(向上增长),每个任务的thread_info结构被分配在stack结束的部位,该结构的成员是指向该任务实际的task_struct结构.
(5)系统使用进程识别值( process identification value or PID)来标识进程.PID是使用一个整形数 pid_t表示的,典型类型是int.通常最大值为32768(short int),可以通过修改/proc/sys/kernel/pid_max来增加最大值.x86架构使用current_thread_info()->task;来获得当前任务的task_struct指针.PowerPC架构则是通过读取寄存器r2来获取task_struct的地址的.
(6)Linux进程的状态有5种:
TASK_RUNNING: 该任务正在运行或者在等待运行的任务队列中.
TASK_INTERRUPTIBLE: 该任务处于睡眠状态,等待某一个条件或信号来唤醒进入TASK_RUNNING状态.
TASK_UNINTERRUPTIBLE: 该状态和TASK_INTERRUPTIBLE一样,区别在于接收到信号后并不被唤醒.使用要比TASK_INTERRUPTIBLE少.
__TASK_TRACE: 该状态表示任务被其他任务跟踪,如debugger,通过ptrace
__TASK_STOPPED: 程序执行被停止,将不会运行或者有资格运行.当任务接收到SIGSTOP,SIGSTP,SIGTTIN,SIGTTOU或者在调试时接收到任何其他信号.
(7) 可以使用set_task_state(task, state); 来修改任务状态.等同于task->state = state;(除SMP系统外), 使用 set_current_state(state) 修改当前任务状态,等同于set_task_state(current, state).
(8) Linux和Unix一样,所有的进程都是init进程的子进程,PID是1.
获取父进程task struct结构指针:
struct task_struct *my_parent = current->parent;
遍历子进程task struct结构指针:
struct task_struct *task;
struct list_head *list;
list_for_each(list, ¤t->children) {
task = list_entry(list, struct task_struct, sibling);
/* task now points to one of current’s children */
}
init进程的描述符保存在init_task,如下代码可以获得init_task
struct task_struct *task;
for (task = current; task != &init_task; task = task->parent)
;
/* task now points to init */
任务链表是一个循环双向链表,要获得链表中下一个任务的指针可以使用:
list_entry(task->tasks.next, struct task_struct, tasks);
获得上一个任务的指针可以使用:
list_entry(task->tasks.prev, struct task_struct, tasks);
这两个函数等同于宏next_task(task)和prev_task(task).
for_each_process(task)用来遍历整个任务链表.
struct task_struct *task;
for_each_process(task) {
/* this pointlessly prints the name and PID of each task */
printk(“%s[%d]\n”, task->comm, task->pid);
}
(9)Process Creation
fork(): 创建一个子进程,该进程是当前进程的一个副本.区别仅仅在于PID不通,PPID,以及一些不能继承的资源和统计信息.如等待的信号.
exec(): 加载一个新的可执行文件到地址空间然后执行它.
fork()使用copy-on-write使子进程和父进程使用同一个拷贝来节约资源开销.数据被写入时才进行复制,资源也是在被写入时进行复制.必须要复制的是父进程的页表和子进程的进程描述符
fork(),vfork(),__clone()都调用clone()这个系统调用,clone()会调用do_fork(),do_fork()会调用copy_process().
vfork()基本和fork()一样,除了父进程的页表不被复制.子进程在父进程的地址空间中作为单独的线程运行,父进程被阻塞直至子进程调用exec()或者exit.理想的系统不需要vfork().
(10) The Linux Implementation of Threads
Linux所有的线程都是标准的进程,仅仅是共享了资源.与Windows等其他系统不同.后者线程相当于轻量级的进程,而在Linux中所有的进程都相当于轻量级的.
线程创建:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
普通的fork()可以实现为:
clone(SIGCHLD, 0);
vfork可以实现为:
clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0);
传递给clone的标识可以定义新的进程的行为和哪些资源是父进程和子进程共享的.这些标志位定义在 <linux/sched.h>.
内核线程:
(11)内核线程
内核是一个存在于内核空间标准的进程,区别于普通进程唯一的在于其没有地址空间,其mm指针是NULL.常见的内核线程有flush().
运行命令 ps -ef可以看到内核进程.
创建新内核线程的API是:
struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[], ...);
可以使用kthread_run来创建并运行内核线程
struct task_struct *kthread_run(int (*threadfn)(void *data),void *data, const char namefmt[], ...);
kthread_run是个宏,调用了kthread_create()和wake_up_process().
内核线程调用do_exit()或者其他内核线程调用kthread_stop来终止该内核线程的运行.
(12)进程结束
进程最终通过do_exit()来终止进程.
(13)最终释放 process descriptor时,系统调用 release_task()释放该进程相关的所有资源.
(14)如果父进程在子进程终止之前终止, Linux调用 do_exit()-->exit_notify()-->forget_original_parent()-->find_new_reaper()来给子进程重新分配新的父进程.
更多推荐
所有评论(0)