解析Linux内核的基本的模块管理与时间管理操作

内核模块管理
Linux设备驱动会以内核模块的形式出现,因此学会编写Linux内核模块编程是学习linux设备驱动的先决条件。

Linux内核的整体结构非常庞大,其包含的组件非常多。我们把需要的功能都编译到linux内核,以模块方式扩展内核功能。

先来看下最简单的内核模块

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

static int __init hello_init(void)
{
    printk(KERN_ALERT "Hello world! %s, %d\n", __FILE__, __LINE__);
    return 0;
} 

static void __exit hello_exit(void)
{ 

    printk(KERN_ALERT "Hello world! %s, %d\n", __FILE__, __LINE__);
} 

module_init(hello_init);
module_exit(hello_exit); 

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mikcy Liu");
MODULE_DESCRIPTION("A simple Module");
MODULE_ALIAS("a simple module");

头文件init.h包含了宏_init和_exit,它们允许释放内核占用的内存。
module_init()和hello_exit()是模块编程中最基本也是必须的两个函数。
module_init()是驱动程序初始化的入口点。
hello_exit是模块的退出和清理函数。此处可以做所有终止该驱动程序时相关的清理工作。

内核模块中用于输出的函数式内核空间的printk()而非用户空间的printf(),printk()的用法和printf()相似,但前者可定义输出级别。printk()可作为一种最基本的内核调试手段

前者可以定义输出级别,在 <内核目录>/include/linux/kernel.h中

#define KERN_EMERG   "<0>"  /* system is unusable          */
#define KERN_ALERT   "<1>"  /* action must be taken immediately   */
#define KERN_CRIT    "<2>"  /* critical conditions         */
#define KERN_ERR    "<3>"  /* error conditions           */
#define KERN_WARNING  "<4>"  /* warning conditions          */
#define KERN_NOTICE   "<5>"  /* normal but significant condition   */
#define KERN_INFO    "<6>"  /* informational            */
#define KERN_DEBUG   "<7>"  /* debug-level messages         */

未设定级别的,在<内核目录>/kernel/printk.c中定义

/* printk's without a loglevel use this.. */
#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */
 #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */

只有当printk打印信息时的loglevel小于DEFAULT_CONSOLE_LOGLEVEL的值(优先级高于console loglevel),这些信息才会被打印到console上。

模块声明与描述

  • 在linux模块中,我们可以使用
  • MODULE_LICENSE(license)   //定义模块的license,一般为GPL,或相关公司的license
  • MODULE_AUTHOR             //模块的作者
  • MODULE_DESCRIPTION        //对模块程序的描述,string
  • MODULE_VERSION            //版本
  • MODULE_DEVICE_TABLE       //模块程序所支持的设备,string
  • MODULE_ALIAS              //别名
  • MODULE_PARM(var,type)     //模块参数

模块编译
首先看看Makefile文件:

obj-m := hello.o
KERNEL_BUILD := /lib/modules/$(shell uname -r)/build
all:
    make -C $(KERNEL_BUILD) M=$(shell pwd) modules
clean:
    -rm -rf *.o *.ko *.mod.c .*.cmd *.order *.symvers .tmpversions
KERNELBUILD :=/lib/modules/$(shell uname -r)/

build是编译内核模块需要的Makefile的路径,Ubuntu下是/lib/modules/2.6.31-14-generic/build

如果是Arm平台的开发板,则-C选项指定的位置(即内核源代码目录),其中保存有内核的顶层Makefile文件.

make -C $(KERNEL_BUILD) M=$(shell pwd) modules 编译内核模块。-C 将工作目录转到KERNEL_BUILD,调用该目录下的Makefile,并向这个Makefile传递参数M的值是$(shell pwd) modules。

M=选项让该makefile在构造modules目标之前返回到模块源代码目录。然后modules目标指向obj-m变量中设定的模块

执行make命令开始编译模块,生成hello.ko,执行make clean可清除编译产生的文件。

1、添加模块

   insmod hello.ko

2、查看模块

  lsmod | grep hello

lsmod命令实际上读取并分析/proc/modules文件,也可以cat /proc/modules文件

在模块所在目录下执行

modinfo  hello.ko可以查看模块信息,如下所示

filename:    hello.ko
alias:     a simple module
description:  A simple Module
author:     Mikcy Liu
license:    GPL
srcversion:   875C95631F4F336BBD4216C
depends:
vermagic:    3.5.0-17-generic SMP mod_unload modversions 686

3、删除模块

 rmmod hello

模块加载函数

Linux内核模块加载函数一般以__init标识声明,典型的模块加载函数的形式如下:

static int __init initialization_function(void) {
   //初始化代码
}
module_init(initialization_function);

模块加载函数必须以“module_init(函数名)”的形式指定。它返回整形值,若初始化成功,应返回0。而在初始化失败时。应该返回错误编码。

在linux内核里,错误编码是一个负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM之类的符号值。返回相应的错误编码是种非常好的习惯,因为只有这样,用户程序才可以利用perror等方法把它们转换成有意义的错误信息字符串。

在linux2.6内核中,所有标识为__init的函数在连接的时候都会放在.init.text(这是module_init宏在目标代码中增加的一个特殊区段,用于说明内核初始化函数的所在位置)这个区段中,此外,所有的__init函数在区段.initcall.init中还保存着一份函数指针,在初始化时内核会通过这些函数指针调用这些__init函数,并在初始化完成后释放init区段(包括.init.text和.initcall.init等)。所以大家应注意不要在结束初始化后仍要使用的函数上使用这个标记。

模块卸载函数

Linux内核卸载模块函数一般以__exit标识声明,典型的模块卸载函数的形式如下:

static void __exit cleanup_function(void) {
 //释放代码
}
module_exit(cleanup_function);

模块卸载函数在模块卸载时被调用,不返回任何值,必须以”module_exit(函数名)”的形式来指定
与__init一样__exit也可以使对应函数在运行完成后自动回收内存。

一般来说,模块卸载函数完成与模块加载函数相反的功能:
如果模块加载函数注册了 XXX模块,则模块卸载函数应注销XXX。
若模块加载函数动体申请了内存,则模块卸载函数应释放该内存。
若模块加载函数申请了硬件资源,则模块卸载函数应释放这些硬件资源。
若模块加载函数开启了硬件,则模块卸载函数应关闭硬件。

内核时间管理
(1)内核中的时间概念
    时间管理在linux内核中占有非常重要的作用。
    相对于事件驱动而言,内核中有大量函数是基于时间驱动的。
    有些函数是周期执行的,比如每10毫秒刷新一次屏幕;
    有些函数是推后一定时间执行的,比如内核在500毫秒后执行某项任务。
    要区分:
    *绝对时间和相对时间
    *周期性产生的事件和推迟执行的事件
    周期性事件是由系统系统定时器驱动的
 
(2)HZ值
    内核必须在硬件定时器的帮助下才能计算和管理时间。
    定时器产生中断的频率称为节拍率(tick rate)。
    在内核中指定了一个变量HZ,内核初始化的时候会根据这个值确定定时器的节拍率。
    HZ定义在<asm/param.h>,在i386平台上,目前采用的HZ值是1000。
    也就是时钟中断每秒发生1000次,周期为1毫秒。即:
    #define HZ 1000
 
    注意!HZ不是个固定不变的值,它是可以更改的,可以在内核源代码配置的时候输入。
    不同的体系结构其HZ值是不一样的,比如arm就采用100。
    如果在驱动中要使用系统的中断频率,直接使用HZ,而不要用100或1000
 
 
    a.理想的HZ值
        i386的HZ值一直采用100,直到2.5版后才改为1000。
        提高节拍率意味着时钟中断产生的更加频繁,中断处理程序也会更频繁地执行。
 
        带来的好处有:
        *内核定时器能够以更高的频率和更高的准确度运行
        *依赖定时器执行的系统调用,比如poll()和select(),运行的精度更高
        *提高进程抢占的准确度
        (缩短了调度延时,如果进程还剩2ms时间片,在10ms的调度周期下,进程会多运行8ms。
        由于耽误了抢占,对于一些对时间要求严格的任务会产生影响)
 
        坏处有:
        *节拍率要高,系统负担越重。
        中断处理程序将占用更多的处理器时间。
 
 (3)jiffies
    全局变量jiffies用于记录系统启动以来产生的节拍的总数。
    启动时,jiffies初始化为0,此后每次时钟中断处理程序都会增加该变量的值。
    这样,系统启动后的运行时间就是jiffies/HZ秒
 
    jiffies定义于<linux/jiffies.h>中:
    extern unsigned long volatile jiffies;
 
    jiffies变量总是为unsigned long型。
    因此在32位体系结构上是32位,而在64位体系上是64位。
    对于32位的jiffies,如果HZ为1000,49.7天后会溢出。
    虽然溢出的情况不常见,但程序在检测超时时仍然可能因为回绕而导致错误。
    linux提供了4个宏来比较节拍计数,它们能正确地处理节拍计数回绕。

  #include <linux/jiffies.h>
  #define time_after(unknown, known)    // unknow > known
  #define time_before(unknown, known)   // unknow < known
  #define time_after_eq(unknown, known)  // unknow >= known
  #define time_before_eq(unknown, known)  // unknow <= known

unknown通常是指jiffies,known是需要对比的值(常常是一个jiffies加减后计算出的相对值)
 
    例:

  unsigned long timeout = jiffies + HZ/2; /* 0.5秒后超时 */
  ...
  if(time_before(jiffies, timeout)){
    /* 没有超时,很好 */
  }else{
    /* 超时了,发生错误 */

time_before可以理解为如果在超时(timeout)之前(before)完成
 
 
    *系统中还声明了一个64位的值jiffies_64,在64位系统中jiffies_64和jiffies是一个值。
    可以通过get_jiffies_64()获得这个值。
 
    *使用

  u64 j2;
    j2 = get_jiffies_64();

(4)获得当前时间
    驱动程序中一般不需要知道墙钟时间(也就是年月日的时间)。但驱动可能需要处理绝对时间。
    为此,内核提供了两个结构体,都定义在<linux/time.h>:

a.

  struct timeval {
   time_t tv_sec; /* seconds */
   suseconds_t tv_usec; /* microseconds */
  };

较老,但很流行。采用秒和毫秒值,保存了1970年1月1日0点以来的秒数

b.

  struct timespec {
   time_t tv_sec; /* seconds */
   long tv_nsec; /* nanoseconds */
  };

较新,采用秒和纳秒值保存时间。
 
    c.do_gettimeofday()
        该函数用通常的秒或微秒来填充一个指向struct timeval的指针变量,原型如下:

    #include <linux/time.h>
    void do_gettimeofday(struct timeval *tv);

d.current_kernel_time()
        该函数可用于获得timespec

#include <linux/time.h>
    struct timespec current_kernel_time(void);

确定时间的延迟执行 
    设备驱动程序经常需要将某些特定代码延迟一段时间后执行,通常是为了让硬件能完成某些任务。
    长于定时器周期(也称为时钟嘀嗒)的延迟可以通过使用系统时钟完成,而非常短的延时则通过软件循环的方式完成
     
 
(1)短延时
    对于那些最多几十个毫秒的延迟,无法借助系统定时器。
    系统通过软件循环提供了下面的延迟函数:

  #include <linux/delay.h>
  /* 实际在<asm/delay.h> */
  void ndelay(unsigned long nsecs); /*延迟纳秒 */
  void udelay(unsigned long usecs); /*延迟微秒 */
  void mdelay(unsigned long msecs); /*延迟毫秒 */

这三个延迟函数均是忙等待函数,在延迟过程中无法运行其他任务。
 
(2)长延时
    a.在延迟到期前让出处理器

    while(time_before(jiffies, j1))
      schedule();

在等待期间可以让出处理器,但系统无法进入空闲模式(因为这个进程始终在进行调度),不利于省电。
 
    b.超时函数

    #include <linux/sched.h>
    signed long schedule_timeout(signed long timeout);

使用方式:

  set_current_state(TASK_INTERRUPTIBLE);
  schedule_timeout(2*HZ); /* 睡2秒 */

进程经过2秒后会被唤醒。如果不希望被用户空间打断,可以将进程状态设置为TASK_UNINTERRUPTIBLE。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/sched.h>
#include <linux/delay.h> 

static int __init test_init(void)
{
  set_current_state(TASK_INTERRUPTIBLE);
  schedule_timeout(5 * HZ);
  printk(KERN_INFO "Hello Micky\n");
  return 0;
} 

static void __exit test_exit(void)
{
} 

module_init(test_init);
module_exit(test_exit); 

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Micky Liu");
MODULE_DESCRIPTION("Test for delay");

(3)等待队列
    使用等待队列也可以实现长延迟。
    在延迟期间,当前进程在等待队列中睡眠。
    进程在睡眠时,需要根据所等待的事件链接到某一个等待队列。
 
    a.声明等待队列
        等待队列实际上就是一个进程链表,链表中包含了等待某个特定事件的所有进程。

    #include <linux/wait.h>
    struct __wait_queue_head {
      spinlock_t lock;
      struct list_head task_list;
    };
    typedef struct __wait_queue_head wait_queue_head_t;

要想把进程加入等待队列,驱动首先要在模块中声明一个等待队列头,并将它初始化。
 
        静态初始化

    DECLARE_WAIT_QUEUE_HEAD(name);

动态初始化

      wait_queue_head_t my_queue;
      init_waitqueue_head(&my_queue);

b.等待函数
        进程通过调用下面函数可以在某个等待队列中休眠固定的时间:

    #include <linux/wait.h>
    long wait_event_timeout(wait_queue_head_t q,condition, long timeout);
    long wait_event_interruptible_timeout(wait_queue_head_t q, condition, long timeout);

调用这两个函数后,进程会在给定的等待队列q上休眠,但会在超时(timeout)到期时返回。
        如果超时到期,则返回0,如果进程被其他事件唤醒,则返回剩余的时间数。
        如果没有等待条件,则将condition设为0
 
        使用方式:

    wait_queue_head_t wait;
    init_waitqueue_head(&wait);
    wait_event_interruptible_timeout(wait, 0, 2*HZ);
    /*当前进程在等待队列wait中睡2秒 */

(4)内核定时器
    还有一种将任务延迟执行的方法是采用内核定时器。
    与前面几种延迟方法不同,内核定时器并不会阻塞当前进程,
    启动一个内核定时器只是声明了要在未来的某个时刻执行一项任务,当前进程仍然继续执行。
    不要用定时器完成硬实时任务
 
    定时器由结构timer_list表示,定义在<linux/timer.h>

  struct timer_list{
    struct list_head entry; /* 定时器链表 */
    unsigned long expires; /* 以jiffies为单位的定时值 */
    spinlock_t lock;
    void(*function)(unsigned long); /* 定时器处理函数 */
    unsigned long data; /* 传给定时器处理函数的参数 */
  }

内核在<linux/timer.h>中提供了一系列管理定时器的接口。
 
    a.创建定时器

    struct timer_list my_timer;

b.初始化定时器

    init_timer(&my_timer);
    /* 填充数据结构 */
    my_timer.expires = jiffies + delay;
    my_timer.data = 0;
    my_timer.function = my_function; /*定时器到期时调用的函数*/

c.定时器的执行函数
        超时处理函数的原型如下:

    void my_timer_function(unsigned long data);

可以利用data参数用一个处理函数处理多个定时器。可以将data设为0
 
    d.激活定时器

    add_timer(&my_timer);

定时器一旦激活就开始运行。
 
    e.更改已激活的定时器的超时时间

    mod_timer(&my_timer, jiffies+ney_delay);

可以用于那些已经初始化但还没激活的定时器,
        如果调用时定时器未被激活则返回0,否则返回1。
        一旦mod_timer返回,定时器将被激活。
 
    f.删除定时器

    del_timer(&my_timer);

被激活或未被激活的定时器都可以使用,如果调用时定时器未被激活则返回0,否则返回1。
        不需要为已经超时的定时器调用,它们被自动删除
 
    g.同步删除

  del_time_sync(&my_timer);

在smp系统中,确保返回时,所有的定时器处理函数都退出。不能在中断上下文使用。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/timer.h> 

struct timer_list my_timer; 

static void timer_handler(unsigned long arg)
{
  printk(KERN_INFO "%s %d Hello Micky! arg=%lu\n",__func__, __LINE__, arg );
} 

static int __init test_init(void)
{
  init_timer(&my_timer); 

  my_timer.expires = jiffies + 5 * HZ;
  my_timer.function = timer_handler;
  my_timer.data = 10;
  add_timer(&my_timer); 

  return 0;
} 

static void __exit test_exit(void)
{
  del_timer(&my_timer);
} 

module_init(test_init);
module_exit(test_exit); 

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Micky Liu");
MODULE_DESCRIPTION("Test for timer"); 

#include <linux/init.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/timer.h> 

struct timer_list my_timer; 

static void timer_handler(unsigned long arg)
{
  printk(KERN_INFO "%s %d Hello Micky! arg=%lu\n",__func__, __LINE__, arg );
} 

static int __init test_init(void)
{
  init_timer(&my_timer); 

  //my_timer.expires = jiffies + 5 * HZ;
  my_timer.function = timer_handler;
  my_timer.data = 10;
  //add_timer(&my_timer);
  mod_timer(&my_timer, jiffies + 5 * HZ); 

  return 0;
} 

static void __exit test_exit(void)
{
  del_timer(&my_timer);
} 

module_init(test_init);
module_exit(test_exit); 

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Micky Liu");
MODULE_DESCRIPTION("Test for timer");

 
不确定时间的延迟执行
(1)什么是不确定时间的延迟
    前面介绍的是确定时间的延迟执行,但在写驱动的过程中经常遇到这种情况:
    用户空间程序调用read函数从设备读数据,但设备中当前没有产生数据。
    此时,驱动的read函数默认的操作是进入休眠,一直等待到设备中有了数据为止。
 
    这种等待就是不定时的延迟,通常采用休眠机制来实现。
 
 
(2)休眠
    休眠是基于等待队列实现的,前面我们已经介绍过wait_event系列函数,
    但现在我们将不会有确定的休眠时间。
 
    当进程被置入休眠时,会被标记为特殊状态并从调度器的运行队列中移走。
    直到某些事件发生后,如设备接收到数据,则将进程重新设为运行态并进入运行队列进行调度。
    休眠函数的头文件是<linux/wait.h>,具体的实现函数在kernel/wait.c中。
 
    a.休眠的规则
        *永远不要在原子上下文中休眠
        *当被唤醒时,我们无法知道睡眠了多少时间,也不知道醒来后是否获得了我们需要的资源
        *除非知道有其他进程会在其他地方唤醒我们,否则进程不能休眠
 
    b.等待队列的初始化
        见前文
 
    c.休眠函数
        linux最简单的睡眠方式为wait_event宏。该宏在实现休眠的同时,检查进程等待的条件。
 
        A.

void wait_event(
       wait_queue_head_t q,
       int condition);

B.

int wait_event_interruptible(wait_queue_head_t q, int condition);

q: 是等待队列头,注意是采用值传递。
            condition: 任意一个布尔表达式,在条件为真之前,进程会保持休眠。
            注意!进程需要通过唤醒函数才可能被唤醒,此时需要检测条件。
            如果条件满足,则被唤醒的进程真正醒来;
            如果条件不满足,则进程继续睡眠。
 
 
    d.唤醒函数
        当我们的进程睡眠后,需要由其他的某个执行线程(可能是另一个进程或中断处理例程)唤醒。
        唤醒函数:
            #include <linux/wait.h>
            1.

 void wake_up(
        wait_queue_head_t *queue);

2.

void wake_up_interruptible(
        wait_queue_head_t *queue);

wake_up会唤醒等待在给定queue上的所有进程。
        而wake_up_interruptible唤醒那些执行可中断休眠的进程。
        实践中,约定做法是在使用wait_event时使用wake_up,而使用wait_event_interruptible时使用wake_up_interruptible。

(0)

相关推荐

  • Linux内核链表实现过程

    关于双链表实现,一般教科书上定义一个双向链表节点的方法如下: 复制代码 代码如下: struct list_node{stuct list_node *pre;stuct list_node *next;ElemType data; } 即一个链表节点包含:一个指向前向节点的指针.一个指向后续节点的指针,以及数据域共三部分.但查看linux内核代码中的list实现时,会发现其与教科书上的方法有很大的差别.来看看linux是如何实现双链表.双链表节点定义 复制代码 代码如下: struct lis

  • Linux内核漏洞浅析

    与Windows相比,Linux被认为具有更好的安全性和其他扩展性能.这些特性使得Linux在操作系统领域异军突起,得到越来越多的重视.随着Linux应用量的增加,其安全性也逐渐受到了公众甚或黑客的关注.那么,Linux是否真的如其支持厂商们所宣称的那样安全呢?本期我们请到了启明星辰信息技术有限公司积极防御实验室工程师赵伟,对Linux进行专业的漏洞技术分析. Linux内核精短.稳定性高.可扩展性好.硬件需求低.免费.网络功能丰富.适用于多种cpu等特性,使之在操作系统领域异军突起.其独特的魅

  • Linux内核模块和驱动的编写

    Linux内核是一个整体是结构,因此向内核添加任何东西,或者删除某些功能,都十分困难.为了解决这个问题引入了内核机制.从而可以动态的想内核中添加或者删除模块. 模块不被编译在内核中,因而控制了内核的大小.然而模块一旦被插入内核,他就和内核其他部分一样.这样一来就会曾家一部分系统开销.同时,如果模块出现问题,也许会带来系统的崩溃. 模块的实现机制: 启动时,由函数 void inti_modules() 来初始化模块,因为启动事很多时候没有模块.这个函数往往把内核自身当作一个虚模块. 如由系统需要

  • SYN Cookie在Linux内核中的实现

    概述 在目前以IPv4为支撑的网络协议上搭建的网络环境中,SYN Flood是一种非常危险而常见的DoS攻击方式.到目前为止,能够有效防范SYN Flood攻击的手段并不多,而SYN Cookie就是其中最著名的一种.SYN Cookie原理由D. J. Bernstain和 Eric Schenk发明.在很多操作系统上都有各种各样的实现.其中包括Linux.本文就分别介绍一下SYN Flood攻击和SYN Cookie的原理,更重要的是介绍Linux内核中实现SYN Cookie的方式.最后,

  • 一张图看尽Linux内核运行原理

    众所周知的是,几乎整个互联网都运行在 Linux 上,从网络协议,到服务器,到你平常访问的绝大多数网站,都能看到它的身影.Linux 内核就是最复杂最流行的开源项目之一.如果你希望学习内核知识,在网上可以搜到无数的资料,但是 Linux 内核还是一个非常难弄明白的项目. 俗话说:一图胜千言,今天我们就为大家介绍一张完整的 Linux 内核运行原理图,通过这张图,你可以很方便地学习内核知识. 在 Linux 内核中,有许多层次.模块.功能调用和函数:要把其中的每一块儿都弄明白很不容易,不过 Mak

  • Linux内核中红黑树算法的实现详解

    一.简介 平衡二叉树(BalancedBinary Tree或Height-Balanced Tree) 又称AVL树.它或者是一棵空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1.若将二叉树上结点的平衡因子BF(BalanceFactor)定义为该结点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有结点的平衡因子只可能是-1.0和1.(此段定义来自严蔚敏的<数据结构(C语言版)>) 红黑树 R-B Tree,全称是Red-B

  • Linux内核启动参数详解

    1.环境: Ubuntu 16.04 Linux linuxidc 4.4.0-89-generic #112-Ubuntu SMP Mon Jul 31 19:38:41 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 2.查看当前linux内核的启动参数: cat /proc/cmdline 笔者的输出内容如下: BOOT_IMAGE=/boot/vmlinuz-4.4.0-89-generic root=UUID=bef418fa-4202-4513-b39

  • 浅谈Linux内核创建新进程的全过程

    进程描述 进程描述符(task_struct) 用来描述进程的数据结构,可以理解为进程的属性.比如进程的状态.进程的标识(PID)等,都被封装在了进程描述符这个数据结构中,该数据结构被定义为task_struct 进程控制块(PCB) 是操作系统核心中一种数据结构,主要表示进程状态. 进程状态 fork() fork()在父.子进程各返回一次.在父进程中返回子进程的 pid,在子进程中返回0. fork一个子进程的代码 #include <stdio.h> #include <stdli

  • 解析Linux内核的基本的模块管理与时间管理操作

    内核模块管理 Linux设备驱动会以内核模块的形式出现,因此学会编写Linux内核模块编程是学习linux设备驱动的先决条件. Linux内核的整体结构非常庞大,其包含的组件非常多.我们把需要的功能都编译到linux内核,以模块方式扩展内核功能. 先来看下最简单的内核模块 #include <linux/init.h> #include <linux/module.h> static int __init hello_init(void) { printk(KERN_ALERT &

  • 解析Linux内核与设备树的编译和烧写

    一.准备材料 可以根据自己的需要准备相应材料: 开发环境:VMware 操作系统:ubuntu 开发版:湃兔i2S-6UB 二.下载Linux内核文件 之前下载过UBoot文件的朋友应该知道,在每个开发版的资料里都有相应的文件,没有的可以找购买开发版的店家要. 下载完成后将文件拷贝到linux系统下进行解压,解压后会的目录如下图所示: 注意:编译时一定要在当前路径下才能编译 三.编译 1.清理项目工程 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf

  • 详解Linux内核内存管理架构

    内存管理子系统可能是linux内核中最为复杂的一个子系统,其支持的功能需求众多,如页面映射.页面分配.页面回收.页面交换.冷热页面.紧急页面.页面碎片管理.页面缓存.页面统计等,而且对性能也有很高的要求.本文从内存管理硬件架构.地址空间划分和内存管理软件架构三个方面入手,尝试对内存管理的软硬件架构做一些宏观上的分析总结. 内存管理硬件架构 因为内存管理是内核最为核心的一个功能,针对内存管理性能优化,除了软件优化,硬件架构也做了很多的优化设计.下图是一个目前主流处理器上的存储器层次结构设计方案.

  • Linux内核设备驱动之内核的时间管理笔记整理

    /****************** * linux内核的时间管理 ******************/ (1)内核中的时间概念 时间管理在linux内核中占有非常重要的作用. 相对于事件驱动而言,内核中有大量函数是基于时间驱动的. 有些函数是周期执行的,比如每10毫秒刷新一次屏幕: 有些函数是推后一定时间执行的,比如内核在500毫秒后执行某项任务. 要区分: *绝对时间和相对时间 *周期性产生的事件和推迟执行的事件 周期性事件是由系统系统定时器驱动的 (2)HZ值 内核必须在硬件定时器的帮

  • Ubuntu中为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序

    在Ubuntu Android简单介绍硬件抽象层(HAL)一文中,我们简要介绍了在Android系统为为硬件编写驱动程序的方法.简单来说,硬件驱动程序一方面分布在Linux内核中,另一方面分布在用户空间的硬件抽象层中.接着Ubuntu Android系统上编写Linux内核驱动程序实现方法一文中举例子说明了如何在Linux内核编写驱动程序.在这一篇文章中,我们将继续介绍Android系统硬件驱动程序的另一方面实现,即如何在硬件抽象层中增加硬件模块来和内核驱动程序交互.在这篇文章中,我们还将学习到

  • 探索Linux内核:Kconfig的秘密

    深入了解Linux配置/构建系统是如何工作的. 自从Linux内核代码迁移到Git之后,Linux内核配置/构建系统(也称为Kconfig/kBuild)已经存在了很长时间.然而,作为支持基础设施,它很少受到关注:即使在日常工作中使用它的内核开发人员也从未真正考虑过它. 为了探索Linux内核是如何编译的,本文将深入研究Kconfig/kBuild内部进程,解释.config文件和vmlinux/bzImage文件是如何生成的,并介绍一个用于依赖性跟踪的智能技巧. Kconfig 构建内核的第一

  • Linux内核设备驱动之字符设备驱动笔记整理

    /******************** * 字符设备驱动 ********************/ (1)字符设备驱动介绍 字符设备是指那些按字节流访问的设备,针对字符设备的驱动称为字符设备驱动. 此类驱动适合于大多数简单的硬件设备.比如并口打印机,我们通过在/dev下建立一个设备文件(如/dev/printer)来访问它. 用户应用程序用标准的open函数打开dev/printer,然后用write向文件中写入数据,用read从里面读数据. 调用流程: write(): 用户空间 -->

  • 解析Linux高性能网络IO和Reactor模型

    目录 一.基本概念介绍 二.网络IO的读写过程 三.Linux五种网络IO模型 3.1.阻塞式I/O (blocking IO) 3.2.非阻塞式I/O (nonblocking IO) 3.3.多路复用I/O (IO multiplexing) 3.4.信号驱动式I/O (SIGIO) 3.5.异步IO (POSIX的aio_系列函数) 四.多路复用IO深入理解一波 4.1.select 4.2.epoll 4.3.epoll相比select的优点 4.4.关于epoll的IO模型是同步异步的

  • 如何修改Linux内核参数vm.swappiness

    目录 修改Linux内核参数vm.swappiness 调整vm.swappiness的方法 了解vm.swappiness 使用交换 vm.swappiness 小结一下吧 总结 修改Linux内核参数vm.swappiness 内核参数vm.swappiness控制换出运行时内存的相对权重,参数值大小对如何使用swap分区有很大联系. 值越大,表示越积极使用swap分区,越小表示越积极使用物理内存. 默认值swappiness=60,表示内存使用率超过100-60=40%时开始使用交换分区.

随机推荐