操作系统=内核+系统程序
系统程序=编译环境+API+AUI
编译环境=编译程序+连接程序+装载程序
API=系统调用+语言库函数
AUI=Shell+系统服务例程+应用程序
应用软件是针对最终用户需求编写的,系统软件是为了简化应用程序的开发而存在的,例如编程语言的执行环境为应用程序开发了提供诸如IO操作、图形库等基础服务。
POSIX表示可移植操作系统接口,是一个国际标准。
linux内核除系统调用外,由5个主要的子系统组成:进程调度、内存管理、虚拟文件系统、网络接口、进程间通信。
linux内核被设计为单内核(monolithic)结构,支持动态加载内核模块,为保证支持新设备而又不会无限的扩大内核规模,linux系统对驱动和新文件系统采用模块化方式,可动态加载和卸载。linux内核还采用了虚拟内存技术使得内存空间达到4GB.此外,linux文件系统还实现了一种抽象文件模型———虚拟文件系统(VFC),该文件系统属于UNIX风格。用户可以在统一界面上访问各种不同格式的文件系统。
2.6.30.1 第一个数字是主版本号,第二个是从版本号,第三个是修订版本号,第四个可选的数字为稳定版本号
内核源代码位于/usr/src/linux
include目录包含了建立内核代码时所需的大部分包含文件
init目录包含核心的初始化代码
arch目录包括linux支持的所有硬件结构的内核代码
drivers目录中是系统中所有的设备驱动程序。
fs 包含了所有文件系统的代码
net目录里是核心的网络部分代码
mm目录包含了所有的内存管理代码
ipc目录包含了进程间的通信代码
Kernel包含了主内核代码
内核模块全称为动态可加载模块LMK,模块机制弥补了单内核可扩展性和可维护性较差的不足。模块是具有独立功能的程序,可被单独编译,不能独立运行。
#include
#include
#include
static int __init lkp_init(void) { //模块初始化函数
printk ("My module worked!\n");
return 0;
}
static void __exit lkp_cleanup(void) { //模块卸载函数
printk ("Unloading my module.\n");}
module_init(lkp_init);
module_exit(lkp_cleanup);
MODULE_ENSE("GPL"); //模块许可声明
(1)moduie.h头文件包含了对模块的结构定义以及模块的版本控制
kernel。h包含了常用的内核函数
init.h包含了宏_init 和_exit
(2)lkp_init ()是模块的初始化函数 lkp_cleanup()是模块的退出和清理函数
(3)printk()是内核定义的,把打印的信息输出到终端。module_init和module_exit是最基本的两个函数。module_init()(ˇˍˇ) 向内核注册模块提供新功能。cleanup_exit注销由模块提供所有的功能
(4)最后一句告诉内核该模块具有GUN公共许可证
假如前面的程序起名 hellomod.c,对于2.6版本的内核模块,其中Makefile文件的基本内容如下
# Makefile2.6
01 |
obj-m += hellomod.o //obj-m : = 是赋值语句,使用目标文件hellomod.o生成模块hellomod.ok |
02 |
#产生hellomod模块的目标文件 |
03 |
CURRENT_PATH:=$(shell pwd) |
04 |
#模块所在的当前路径 |
05 |
LINUX_KERNEL:=$(shell uname -r) |
06 |
#内核源代码的当前版本 |
07 |
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL) |
08 |
#内核源代码的绝对路径 |
09 |
all: |
10 |
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules |
11 |
#编译模块 |
12 |
clean: |
13 |
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean |
14 # 清理模块
我们首先获得当前的相对路径(可以在终端输入pwd试一下这个命令),然后再获得当前内核的版本号,这样就可以直接获得当前内核的绝对路径。
完成上述两个文件后,在当前目录下运行make命令,就会生成hello.ko文件,即模块目标文件。
insmod命令可以使我们写的这个模块加入到内核中,但是一般我们要加上sudo。rmmod卸载这个模块。
C语言中一个基本的双向链表定义如下
struct my_list{
void *mydata;
struct my_list *next;
struct my_list *prev;
};
选取双向链表作为基本数据结构并将其镶嵌到其他数据结构中,从而演化成其他复杂数据结构。
struct list-head {
struct list_head *next , *prev
}
这个不含数据域的链表可以嵌入到任何结构,例如: struct my_list {
void my_data;
struct list_head list;
}
struct list_head只定义了链表结点,没有专门定义链表头。内核代码list.h定义了两个宏
#define LIST_HEAD_INIT(name) { &(name), &(name) } 初始化
#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) 声明并初始化
如果要声明并初始化自己的链表头,直接调用 LIST_HEAD
LIST_HEAD( mylist_head)
调用之后,next和prev指针都初始化指向自己。
static inline void list_add(struct list_head *new, struct list_head *head);
{
next->prev = new ; |
|
new ->next = next; |
|
new ->prev = prev; |
|
|
static inline void list_add_tail(struct list_head *new, struct list_head *head);
调用这个内部函数以分别子链表头和尾增加结点
static inline void list_add( struct list_head * new , struct list_head *head) |
|
{ |
|
__list_add( new , head, head->next); } |
该函数向指定链表的head结点后插入new结点。可将任何结点传递给head,但是如果传递最后一个元素给head,该函数可以用来实现一个栈
static inline void list_add_tail(struct list_head *new, struct list_head *head);
{
_list_add(new, head - >prev,head); }
该函数向指定链表的head结点前插入new结点。可将任何结点传递给head,但是如果将第一个元素给head,该函数可以用来实现一个队列。
list.h中定义了如下遍历链表。
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); \
pos = pos->next )
这种遍历仅仅找到一个个结点在链表中的偏移位置,问题是如何通过pos获得结点的起始地址,从而引用该结点的域。list.h中定义了
#define list_entry(ptr, type, member)\
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
指针ptr指向结构体type中的成员member;通过指针ptr,返回结构体type的起始地址,也就是list—entry返回指向type类型的指针。
((unsigned long)(&((type *)0)->member把0地址转化为type结构的指针,然后获取该结构中的成员member域的指针,也就是获得了member在type结构中的偏移量。 其中(char*)(ptr)求出的是ptr的绝对地址,二者相减,于是得到了type类型结构体的起始地址 。
Makefile
尽管其是内核代码的头文件,但稍加修改可移植到用户空间使用。