LDD3学习笔记

1 Tasks [1/1]

  • [X] Porting to linux kernel 3.13

2 Source Code说明

2.1 驱动说明

2.1.1 scull0 to scull3

每个包含一段内存区域,这段内存是全局的,且是可持久的。

2.1.2 scullpipe0 to scullpipe3

FIFO(First In First Out)设备,像管道一样工作。

2.1.3 scullsingle, scullpriv, sculluid, scullwuid

  • scullsingle 每次只允许一个进程访问该设备
  • scullpriv 对每个虚拟终端,都是私有的。
  • sculluid, scullwuid 可被打开多次, 但一次只能由一个用户打开

2.1.4 scull device的数据结构示意图

layout_of_a_scull_device.png

2.1.5 数据结构定义

/*
 * Representation of scull quantum sets.
 */
struct scull_qset {
        void **data;
        struct scull_qset *next;
};

struct scull_dev {
        struct scull_qset *data;  /* Pointer to first quantum set */
        int quantum;              /* the current quantum size */
        int qset;                 /* the current array size */
        unsigned long size;       /* amount of data stored here */
        unsigned int access_key;  /* used by sculluid and scullpriv */
        struct semaphore sem;     /* mutual exclusion semaphore     */
        struct cdev cdev;         /* Char device structure              */
};

2.2 学习笔记

2.2.2 调试技术

  1. 内核常见的调试项:
    1. CONFIG_DEBUG_KERNEL 打开此项后,就可以使其他的一些调试选项可见。
    2. CONFIG_DEBUG_SLAB 检测内存越界或是未初始化等错误。
    3. CONFIG_DEBUG_PAGEALLOC 当释放时,完整的页会从内核地址空间删除。
    4. CONFIG_DEBUG_SPINLOCK 检测对一个未初始化的spinlock的操作。
    5. CONFIG_DEBUG_SPINLOCK_SLEEP 检测当拥有一个spinlock时,还试图进入sleep的代码。
    6. CONFIG_INIT_DEBUG 检测试图访问_init(或_initdata)标记的代码。
    7. CONFIG_DEBUG_INFO 将完整的调试信息统进内核。特别是想使用gdb调试内核时,非常有用。
    8. CONFIG_MAGIC_SYSRQ 启用"magic SysRq"键。
    9. CONFIG_DEBUG_STACKOVERFLOW
    10. CONFIG_DEBUG_STACK_USAGE 有助于跟踪内核栈溢出的问题。
    11. CONFIG_KALLSYMS 将内核符号信息编译进内核。发生oops时,打印调用栈时可以看到函 数符号信息,而不是一些十六进制的数字。
    12. CONFIG_IKCONFIG
    13. CONFIG_IKCONFIG_PROC 打开后,会将完整的内核配置状态编进内核,这样可以通过/proc/目 录下的一些文件查看。
    14. CONFIG_ACPI_DEBUG ACPI调试信息。
    15. CONFIG_DEBUG_DRIVER 打开驱动核心代码的日志输出。
    16. CONFIG_SCSI_CONSTANTS SCSI驱动调试时使用。
    17. CONFIG_INPUT_EVBUG 打开输入事件的一些输出。调试输入设备驱动时使用
    18. CONFIG_PROFILING 性能调优或跟踪内核Hang住等相关问题。
  2. prink

    printk 函数将消息发送到一个环形Buffer:_LOGBUFLEN字节长,大 小从4 KB到1 MB大小。可以通过syslog或读取/proc/kmsg来获取该buffer 中的信息。其中,读取/proc/kmsg的方式会消耗掉buffer中的数据。

  3. rate limit

    有时,同一个Log信息打印太多,会影响Log分析,可以通过 printk_ratelimit() 来控制一条Log的打印次数。 使用方法一般如下所示:

    if (printk_ratelimit( ))
      printk(KERN_NOTICE "The printer is still on fire\n");
    

    可以通过如下两种方法修改 printk_ratelimit() 的行为:

    1. 修改"/proc/sys/kernel/printkratelimit" 重新enable消息打印前, 等待的时间。
    2. "/proc/sys/kernel/printkratelimitburst": 在启用ratelimit前, 允许同一份Log打印的次数。
  4. 打印设备号
    int print_dev_t(char *buffer, dev_t dev);
    char *format_dev_t(char *buffer, dev_t dev);
    

2.2.3 字符设备

2.2.5 休眠与唤醒

  • 基本调用步骤:
    1. 初始化一个等待队列头:

      init_waitqueue_head(&ret->wait_queue);

      注: 判断队列是否为空: waitqueue_active(...) , 返回false即表示队列为空.

    2. 等待某个条件发生:

      wait_event(...)wait_event_timeout(...)

    3. 唤醒队列

      wake_up_all(...)

  • 手动睡眠
    1. 初始化一个等待队列项
      DEFINE_WAIT(my_wait);
      

      或者

      wait_queue_t my_wait;
      init_wait(&my_wait);
      
    2. 将等待队列项加入到队列中
      void prepare_to_wait(wait_queue_head_t *queue,
                           wait_queue_t *wait,
                           int state);
      
    3. 调用 prepare_to_wait 后,可以调用 schedule()
    4. 当schedule返回,执行清理工作。
      void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);
      

2.2.6 计时器与延时

  • 内核变量——Jiffies jiffies与struct timeval, struct timespec之间的转换:
    #include <linux/time.h>
    unsigned long timespec_to_jiffies(struct timespec *value);
    void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);
    unsigned long timeval_to_jiffies(struct timeval *value);
    void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);
    

    读取64-bit的jiffies值:

    #include <linux/jiffies.h>
    u64 get_jiffies_64(void);
    

    turn a wall-clock time into a jiffies value:

    #include <linux/time.h>
    unsigned long mktime (unsigned int year, unsigned int mon,
                          unsigned int day, unsigned int hour,
                          unsigned int min, unsigned int sec);
    

    获取绝对时间:

    #include <linux/time.h>
    void do_gettimeofday(struct timeval *tv);
    
    //获取当前时间
    #include <linux/time.h>
    struct timespec current_kernel_time(void);
    
  • 延迟运行
    1. Busy Waiting

      The HZ symbol specifies the number of clock ticks generated per second.

      while (time_before(jiffies, j1))
        cpu_relax( );
      
    2. schedule
      while (time_before(jiffies, j1)) {
        schedule( );
      }
      
    3. Timeouts
      #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);
      
      #include <linux/sched.h>
      signed long schedule_timeout(signed long timeout);
      set_current_state(TASK_INTERRUPTIBLE);
      schedule_timeout (delay);
      
    4. 短延时
      //busy waiting
      #include <linux/delay.h>
      void ndelay(unsigned long nsecs);
      void udelay(unsigned long usecs);
      void mdelay(unsigned long msecs);
      
      //no busy waiting
      void msleep(unsigned int millisecs);
      unsigned long msleep_interruptible(unsigned int millisecs);
      void ssleep(unsigned int seconds)
      
    5. 内核定时器与延时
      #include <linux/timer.h>
      struct timer_list {
      /* ... */
      unsigned long expires;
      void (*function)(unsigned long);
        unsigned long data;
      };
      void init_timer(struct timer_list *timer);
      struct timer_list TIMER_INITIALIZER(_function, _expires, _data);
      void add_timer(struct timer_list * timer);
      int del_timer(struct timer_list * timer);
      int mod_timer(struct timer_list *timer, unsigned long expires);//update timer
      //Works like del_timer, but also guarantees that when it returns, the timer
      //function is not running on any CPU.
      int del_timer_sync(struct timer_list *timer);
      /*
        Returns true or false to indicate whether the timer is currently scheduled to run
      by reading one of the opaque fields of the structure.
      */
      int timer_pending(const struct timer_list * timer);
      
    6. 下半部机制之微线程

      数据结构:

      #include <linux/interrupt.h>
      struct tasklet_struct {
        /* ... */
        void (*func)(unsigned long);
        unsigned long data;
      };
      void tasklet_init(struct tasklet_struct *t,
      void (*func)(unsigned long), unsigned long data);
      DECLARE_TASKLET(name, func, data);
      DECLARE_TASKLET_DISABLED(name, func, data);
      
    7. 下半部机制之工作队列
      struct workqueue_struct *create_workqueue(const char *name);//为每个CPU创建一个内核线程
      struct workqueue_struct *create_singlethread_workqueue(const char *name);//只创建一个内核线程
      
      // work_struct
      DECLARE_WORK(name, void (*function)(void *), void *data);
      INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
      PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data); //修改work_struct
      
      //submit work
      int queue_work(struct workqueue_struct *queue, struct work_struct *work);
      int queue_delayed_work(struct workqueue_struct *queue,
                             struct work_struct *work, unsigned long delay);
      //cancel pending workqueue entry
      int cancel_delayed_work(struct work_struct *work);
      
      //make sure the work function is not running
      //anywhere in the system after cancel_delayed_work returns 0
      void flush_workqueue(struct workqueue_struct *queue);
      
      //get rid of a workqueue
      void destroy_workqueue(struct workqueue_struct *queue);
      
    8. 共享工作队列

      大部分情况下,我们不需要创建自己的工作队列,可以将工作项提交 到默认的工作队列中。

      int schedule_work(struct work_struct *work);
      
      //submit work
      int schedule_delayed_work(struct work_struct *work, unsigned long delay);
      
      //cancel delayed work
      int cancel_delayed_work(struct work_struct *work);
      
      //flush the shared workqueue
      void flush_scheduled_work(void);
      

2.2.8 与硬件通信

2.2.11 网络驱动

  1. snull驱动设计说明
    • it simulates conversations with real remote hosts in order to better demonstrate the task of writing a network driver.
    • it supports only IP traffic.
    • The snull module creates two interfaces.
    • How a host sees its interfaces

      snull.png

    • possible configuration

      /etc/networks

      network ip
      snullnet0 192.168.0.0
      snullnet1 192.168.1.0

      /etc/hosts

      ip host
      192.168.0.1 local0
      192.168.0.2 remote0
      192.168.1.2 local1
      192.168.1.1 remote1
  2. 网络驱动基本知识
    • struct net_device 描述一个网络接口。头文件: <linux/netdevice.h>
      struct net_device *alloc_netdev(int sizeof_priv,
                                      const char *name,
                                      void (*setup)(struct net_device *));
      
    • 注册网络设备
      for (i = 0; i < 2; i++)
        if ((result = register_netdev(snull_devs[i])))
          printk("snull: error %i registering device \"%s\"\n",
                 result, snull_devs[i]->name);
      
    • netif_start_queue/netif_stop_queue 标记Driver是否可以传输数据包。
      /*
        If you must disable packet transmission from anywhere other than your hard_start_xmit
        function (in response to a reconfiguration request, perhaps), the function you want to
        use is:
      */
      void netif_tx_disable(struct net_device *dev);
      
      /*
        This function is just like netif_start_queue, except that it also pokes the networking
      system to make it start transmitting packets again.
      */
      void netif_wake_queue(struct net_device *dev);
      
    • 数据传输与接收 当可以传输数据时,内核会调用 ndo_start_transmit 将数据放到队 列中。
      netif_rx(skb);//hand off the socket buffer to the upper layers.
      netif_receive_skb(skb); //feed packets to the kernel, used in poll mode
      
    • chagnes in link state
      void netif_carrier_off(struct net_device *dev);
      void netif_carrier_on(struct net_device *dev);
      int netif_carrier_ok(struct net_device *dev);
      
    • ioctl

      Any ioctl command that is not recognized by the protocol layer is passed to the device layer. These device-related ioctl commands accept a third argument from user space, a struct ifreq * .

2.3 引用

Date: 2015-12-17

Author: Fu Yajun

Created: 2016-01-28 四 13:44

Emacs 24.5.1 (Org mode 8.2.10)

Validate