linux操作系统原理与应用 一

操作系统=内核+系统程序

系统程序=编译环境+API+AUI

编译环境=编译程序+连接程序+装载程序

API=系统调用+语言库函数

AUI=Shell+系统服务例程+应用程序

  应用软件是针对最终用户需求编写的,系统软件是为了简化应用程序的开发而存在的,例如编程语言的执行环境为应用程序开发了提供诸如IO操作、图形库等基础服务。

   

 

  POSIX表示可移植操作系统接口,是一个国际标准。

0. linux内核的体系结构

      linux内核除系统调用外,由5个主要的子系统组成:进程调度、内存管理、虚拟文件系统、网络接口、进程间通信。

 

 
1.linux内核的技术特点

  linux内核被设计为单内核(monolithic)结构,支持动态加载内核模块,为保证支持新设备而又不会无限的扩大内核规模,linux系统对驱动和新文件系统采用模块化方式,可动态加载和卸载。linux内核还采用了虚拟内存技术使得内存空间达到4GB.此外,linux文件系统还实现了一种抽象文件模型———虚拟文件系统(VFC),该文件系统属于UNIX风格。用户可以在统一界面上访问各种不同格式的文件系统。

2.liunx内核的版本

   2.6.30.1 第一个数字是主版本号,第二个是从版本号,第三个是修订版本号,第四个可选的数字为稳定版本号

 

3.linux内核源代码的结构

   内核源代码位于/usr/src/linux

   include目录包含了建立内核代码时所需的大部分包含文件

   init目录包含核心的初始化代码

   arch目录包括linux支持的所有硬件结构的内核代码

   drivers目录中是系统中所有的设备驱动程序。

   fs 包含了所有文件系统的代码

           net目录里是核心的网络部分代码

           mm目录包含了所有的内存管理代码

           ipc目录包含了进程间的通信代码

           Kernel包含了主内核代码

4.内核源码分析工具
1.linux超文本交叉代码检索工具LXR
2.windows平台下的源代码阅读工具 Source Insight
5.linux内核模块编程入门

         内核模块全称为动态可加载模块LMK,模块机制弥补了单内核可扩展性和可维护性较差的不足。模块是具有独立功能的程序,可被单独编译,不能独立运行。

        

#include <linux/module.h> 
#include <linux/config.h> 
#include <linux/init.h>

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公共许可证

 

     编译模块 Makefile 文件

     假如前面的程序起名 hellomod.c,对于2.6版本的内核模块,其中Makefile文件的基本内容如下

      # Makefile2.6

01obj-m += hellomod.o                             //obj-m : = 是赋值语句,使用目标文件hellomod.o生成模块hellomod.ok 
02#产生hellomod模块的目标文件
03CURRENT_PATH:=$(shell pwd)
04#模块所在的当前路径
05LINUX_KERNEL:=$(shell uname -r)
06#内核源代码的当前版本 
07LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
08#内核源代码的绝对路径
09all:
10    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
11#编译模块 
12clean:
13    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

14    #  清理模块

              我们首先获得当前的相对路径(可以在终端输入pwd试一下这个命令),然后再获得当前内核的版本号,这样就可以直接获得当前内核的绝对路径。

       完成上述两个文件后,在当前目录下运行make命令,就会生成hello.ko文件,即模块目标文件。 

       运行代码

       insmod命令可以使我们写的这个模块加入到内核中,但是一般我们要加上sudo。rmmod卸载这个模块。

 

 

 

5.linux内核中链表的实现(http://edsionte.com/techblog/archives/1074

        C语言中一个基本的双向链表定义如下

        struct   my_list{

              void  *mydata;

              struct my_list *next;

              struct my_list *prev;

              };

         选取双向链表作为基本数据结构并将其镶嵌到其他数据结构中,从而演化成其他复杂数据结构。

         1.linux内核对链表的抽象定义

        struct list-head {

               struct list_head *next , *prev

              }

        这个不含数据域的链表可以嵌入到任何结构,例如: struct my_list {

                                                                                                        void my_data;

                                                                                                        struct list_head list;

                                                                                                          }

        2.链表的声明与定义

        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指针都初始化指向自己。

      3.链表中增加一个结点

          static inline void list_add(struct list_head *new, struct list_head *head);

                                        {

         next->prev = new;
        new->next = next;
        new->prev = prev;

        prev->next = new;

            }


          static inline void list_add_tail(struct list_head *new, struct list_head *head);

 

         调用这个内部函数以分别子链表头和尾增加结点

          

             static inlinevoid list_add(structlist_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,该函数可以用来实现一个队列。

        4.遍历链表

        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类型结构体的起始地址 。

6.链表的应用

                           

  1. #include<linux/kernel.h>
  2. #include<linux/module.h>
  3. #include<linux/slab.h>
  4. #include<linux/list.h>
  5. MODULE_LICENSE("GPL");
  6. MODULE_AUTHOR("XIYOU");
  7. #define N 10                                               //l链表结点数
  8. struct numlist{
  9.    
  10.         int num;                                                      //数据
  11.         struct list_head list;                                      //指向双链表前后结点的指针
  12. };
  13. struct numlist numhead;                                   //头结点
  14. static int __init doublelist_init(void)
  15. {     //初始化头结点
  16.     struct numlist *listnode;                                //每次申请链表结点时所有的指针
  17.     struct list_head *pos;
  18.     struct numlist *p;
  19.     int i;
  20.     printk("doublelist is starting..\n");
  21.     INIT_LIST_HEAD(&numhead.list);
  22. //建立N个结点,依次加入到链表当中
  23.     for(i=0;i<N;i++)
  24.     {
  25.         listnode=(struct numlist*)kmalloc(sizeof(struct numlist),GFP_KERNEL);   //kmallox在内核空间中申请内存,类似于malloc()
  26.         listnode->num=i+1;
  27.         list_add_tail(&listnode->list,&numhead.list);
  28.         printk("node %d has added to the doublelist\n",i+1);
  29.     }
  30.     //遍历链表
  31.     i=1;
  32.     list_for_each(pos,&numhead.list){
  33.         p=list_entry(pos,struct numlist,list);
  34.         printk("node %d's data :%d\n",i,p->num);
  35.         i++;
  36.     }
  37.     return 0;
  38. }
  39. static void __exit doublelist_exit(void)
  40. {
  41.     struct list_head *pos,*n;
  42.     struct numlist *p;
  43.     //依次删除N个节点
  44.     int i;
  45.     list_for_each_safe(pos,n,&numhead.list)                                    //为了安全删除结点而进行的遍历
  46.     {
  47.         list_del(pos);                                                                               //从双链表中删除当前结点
  48.         p=list_entry(pos,struct numlist,list);                                             //得到当前数据结点的首地址,即指针
  49.         kfree(p);                                                                                                //释放数据结点所占空间
  50.         printk("node %d has removed from the doublelist\n",i++);
  51.     }
  52.     printk("doublelist is exiting\n");

  53. }
  54. module_init(doublelist_init);
    module_exit(doublelist_exit);

 

           Makefile

           

  1. obj-m:=list.o
  2. CURRENT_PATH:=$(shell pwd)
  3. LINUX_KERNEL:=$(shell uname-r)
  4. LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
  5. all:
  6.     make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
  7. clean:
  8.     make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

 

详细阅读 linclude/linux/list.h中的代码

尽管其是内核代码的头文件,但稍加修改可移植到用户空间使用。

 

 

           

 

 

 

   

 

 

 

 

第1章 操作系统概述 1 1.1 认识操作系统 1 1.1.1 从使用者角度 1 1.1.2 从程序开发者角度 2 1.1.3 从操作系统在整个计算机系统中所处位置 2 1.1.4 从操作系统设计者的角度 3 1.2 操作系统的发展 4 1.2.1 操作系统的演变 4 1.2.2 硬件的发展轨迹 5 1.2.3 软件的轨迹 6 1.2.4 单内核与微内核操作系统 7 1.3 开放源代码的Unix/Linux操作系统 8 1.3.1 Unix的诞生和发展 8 1.3.2 Linux的诞生 9 1.3.3 操作系统标准POSIX 9 1.3.4 GNU和Linux 9 1.3.5 Linux的开发模式 10 1.4 Linux内核 10 1.4.1 Linux内核的位置 10 1.4.2 Linux内核的作用 11 1.4.3 Linux内核子系统 11 1.5 Linux内核源代码 13 1.5.1 多版本的内核源代码 13 1.5.2 Linux内核源代码的结构 13 1.5.3 Linux内核源代码分析工具 14 习题1 15 第2章 内存寻址 17 2.1 内存寻址简介 17 2.1.1 Intel x86 CPU寻址方式的演变 18 2.1.2 IA32寄存器简介 19 2.1.3 物理地址、虚拟地址及线性地址 21 2.2 分段机制 22 2.2.1 地址转换及保护 24 2.2.2 Linux中的段 24 2.3 分页机制 25 2.3.1 页与页表 25 2.3.2 线性地址到物理地址的转换 28 2.3.3 分页示例 28 2.3.4 页面高速缓存(cache) 29 2.3.5 Linux中的分页机制 30 2.4 Linux中的汇编语言 31 2.4.1 AT&T与Intel汇编语言的比较 31 2.4.2 AT&T汇编语言的相关知识 32 2.5 Linux系统地址映射示例 33 习题2 35 第3章 进程 37 3.1 进程介绍 37 3.1.1 程序和进程 37 3.1.2 进程的层次结构 38 3.1.3 进程状态 39 3.1.4 进程实例 40 3.2 进程控制块 41 3.2.1 进程状态 42 3.2.2 进程标识符 43 3.2.3 进程之间的亲属关系 43 3.2.4 进程控制块的存放 44 3.3 进程的组织方式 45 3.3.1 进程链表 45 3.3.2 散列表 46 3.3.3 可运行队列 47 3.3.4 等待队列 47 3.4 进程调度 48 3.4.1 基本原理 48 3.4.2 时间片 50 3.4.3 Linux进程调度时机 50 3.4.4 进程调度的依据 51 3.4.5 调度函数schedule()的实现 52 3.5 进程的创建 54 3.5.1 创建进程 55 3.5.2 线程及其创建 56 3.6 与进程相关的系统调用及其应用 58 3.6.1 fork系统调用 58 3.6.2 exec系统调用 59 3.6.3 wait系统调用 60 3.6.4 exit系统调用 62 3.6.5 进程的一生 63 3.7 与调度相关的系统调用及应用 63 习题3 65 第4章 内存管理 67 4.1 Linux的内存管理概述 67 4.1.1 虚拟内存、内核空间和用户空间 67 4.1.2 虚拟内存实现机制间的关系 69 4.2 进程用户空间的管理 70 4.2.1 进程用户空间的描述 71 4.2.2 进程用户空间的创建 74 4.2.3 虚存映射 76 4.2.4 进程的虚存区示例 76 4.2.5 与用户空间相关的系统调用 78 4.3 请页机制 79 4.3.1 缺页异常处理程序 79 4.3.2 请求调页 81 4.3.3 写时复制 83 4.4 物理内存的分配与回收 83 4.4.1 伙伴算法 85 4.4.2 物理页面的分配 86 4.4.3 物理页面的回收 88 4.4.4 slab分配模式 89 4.4.5 内核空间非连续内存区的分配 93 4.5 交换机制 95 4.5.1 交换的基本原理 95 4.5.2 页面交换守护进程kswapd 99 4.6 内存管理实例 99 4.6.1 相关背景知识 100 4.6.2 代码体系结构介绍 100 4.6.3 实现步骤 103 4.6.4 程序代码 103 习题4 108 第5章 中断和异常 110 5.1 中断的基本知识 110 5.1.1 中断向量 110 5.1.2 外设可屏蔽中断 111 5.1.3 异常及非屏蔽中断 112 5.1.4 中断描述符表 112 5.1.5 相关汇编指令 113 5.2 中断描述符表的初始化 114 5.2.1 IDT表项的设置 114 5.2.2 对陷阱门和系统门的初始化 115 5.2.3 中断门的设置 116 5.3 中断处理 116 5.3.1 中断和异常的硬件处理 116 5.3.2 中断请求队列的建立 117 5.3.3 中断处理程序的执行 119 5.3.4 从中断返回 121 5.4 中断的下半部处理机制 121 5.4.1 为什么把中断分为两部分来处理 122 5.4.2 小任务机制 122 5.4.3 下半部 124 5.4.4 任务队列 125 5.5 中断应用——时钟中断 125 5.5.1 时钟 125 5.5.2 时钟运作机制 126 5.5.3 Linux的时间系统 127 5.5.4 时钟中断处理程序 128 5.5.5 时钟中断的下半部处理 129 5.5.6 定时器及其应用 129 习题5 132 第6章 系统调用 133 6.1 系统调用与应用编程接口、系统命令、内核函数的关系 133 6.1.1 系统调用与API 133 6.1.2 系统调用与系统命令 134 6.1.3 系统调用与内核函数 134 6.2 系统调用处理程序及服务例程 135 6.2.1 初始化系统调用 136 6.2.2 system_call()函数 136 6.2.3 参数传递 137 6.2.4 跟踪系统调用的执行 139 6.3 封装例程 140 6.4 添加新系统调用 141 6.5 实例——利用系统调用实现一个调用日志收集系统 143 6.5.1 代码体系结构 143 6.5.2 把代码集成到内核中 146 6.5.3 实现步骤 148 习题6 148 第7章 内核中的同步 149 7.1 临界区和竞争状态 149 7.1.1 临界区举例 149 7.1.2 共享队列和加锁 150 7.1.3 确定保护对象 151 7.1.4 死锁 152 7.1.5 并发执行的原因 153 7.2 内核同步方法 153 7.2.1 原子操作 153 7.2.2 自旋锁 155 7.2.3 信号量 156 7.3 并发控制实例 157 7.3.1 内核任务及其并发关系 158 7.3.2 实现机制 158 7.3.3 关键代码解释 162 7.3.4 实现步骤 163 习题7 164 第8章 文件系统 165 8.1 Linux文件系统基础 165 8.1.1 Linux文件结构 165 8.1.2 Linux文件系统 166 8.1.3 文件类型 167 8.1.4 文件访问权限 168 8.2 虚拟文件系统 168 8.2.1 虚拟文件系统的引入 168 8.2.2 VFS中的数据结构 170 8.2.3 VFS超级块数据结构 171 8.2.4 VFS的索引节点 173 8.2.5 目录项对象 174 8.2.6 与进程相关的文件结构 176 8.2.7 主要的数据结构之间的关系 179 8.3 文件系统的注册、安装与卸载 180 8.3.1 文件系统的注册和注销 180 8.3.2 文件系统的安装 181 8.3.3 文件系统的卸载 183 8.4 页缓冲区 183 8.4.1 address_space对象 183 8.4.2 address_space对象的操作函数表 184 8.5 文件的打开与读写 185 8.5.1 打开文件 185 8.5.2 读写文件 187 8.6 编写一个文件系统 189 8.6.1 Linux文件系统的实现要素 189 8.6.2 什么是romfs文件系统 191 8.6.3 romfs文件系统的布局与文件结构 191 8.6.4 具体实现的对象 192 习题8 195 第9章 设备驱动 196 9.1 概述 196 9.2 设备驱动程序基础 198 9.2.1 I/O端口 199 9.2.2 设备文件 200 9.2.3 中断处理 201 9.2.4 设备驱动程序框架 203 9.3 字符设备驱动程序 204 9.3.1 字符设备驱动程序的注册 204 9.3.2 简单的字符设备驱动程序示例 205 9.4 块设备驱动程序 208 9.4.1 块设备驱动程序的注册 209 9.4.2 块设备请求 212 习题9 215 附录A 内核中的链表 216 A.1 链表数据结构简介 216 A.2 内核链表数据结构的定义及初始化 217 A.3 操作链表的接口 218 A.4 遍历链表 219 附录B 内核模块 221 B.1 什么是模块 221 B.2 编写一个简单的模块 221 B.3 模块编程的基础知识 222 B.4 模块的编译 224 B.5 模块实用程序modutils 226 附录C Linux内核编译 228 C.1 内核简介 228 C.2 为什么重新编译内核 228 C.3 内核编译模式 229 C.4 新版本内核的获取和更新 229 C.5 内核编译 230 C.6 修改并重启管理器 232 附录D Linux编程基础(C语言环境) 233 D.1 Linux编程常识 233 D.1.1 相关标准(ANSI C、POSIX、SVID、XPG) 233 D.1.2 函数库和系统调用 234 D.1.3 在线文档(man、info、HOWTO) 235 D.1.4 C语言编程风格 237 D.2 Linux上的C/C++编译器和调试器 238 D.2.1 运行gcc/egcs 238 D.2.2 gcc/egcs的主要选项 240 D.2.3 gdb简介 240 D.2.4 gdb的常用命令 241 D.2.5 gdb使用示例 242 D.3 GNU make和makefile 243 D.3.1 GNU make 243 D.3.2 makefile的基本结构 243 D.3.3 makefile的变量 244 D.3.4 GNU make的主要预定义变量 245 D.3.5 GNU make的隐含规则 245 D.3.6 运行make 246
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值