Linux設備驅動學習-first_drv.ko

1、linux內核模塊簡介node

linux內核總體結構很是龐大,其包含的組件也很是多。咱們怎麼把須要的部分都包含在內核中呢?linux

     一種辦法是把全部的須要的功能都編譯到內核中。這會致使兩個問題,一是生成的內核會很大,二是若是咱們要在現有的內核中新增或刪除功能,不得不從新編譯內核,工做效率會很是的低,同時若是編譯的模塊不是很完善,頗有可能會形成內核崩潰。算法

2、模塊特色:函數

1)模塊自己並不被編譯入內核,從而控制了內核的大小。測試

 2)模塊一旦被加載,他就和內核中的其餘部分徹底同樣。ui

    注意:模塊並非驅動的必要形式:即:驅動不必定必須是模塊,有些驅動是直接編譯進內核的;同時模塊也不全是驅動,例如咱們寫的一些很小的算法能夠做爲模塊編譯進內核,但它並非驅動。就像燒餅不必定是圓的,圓的也不都是燒餅同樣。this

3、韋東山老師的first_drv.ko分析spa

示例1日誌

#include <linux/module.h>  /* __init __exit */
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>/* printk() */
#include <linux/delay.h>
...

static int first_drv_open(struct inode *inode,struct file *file)
{
    printk("first_drv_open\n");
    return 0;    
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    printk("first_drv_write\n");
    return 0;
}
static int  first_drv_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
       printk("first_drv_read\n");
    return 0;
}
/* 這個結構是字符設備驅動程序的核心
 * 當應用程序操做設備文件時所調用的open、read、write等函數,
 * 最終會調用這個結構中指定的對應函數
 */
static struct file_operations first_drv_fops = 
{
    .owner = THIS_MODULE,/* 這是一個宏,推向編譯模塊時自動建立的__this_module變量 */
       .open  = first_drv_open,
       .read = first_drv_read,
       .write = first_drv_write,
};
 /*模塊加載函數,經過insmod命令加載模塊時,被自動執行*/
static int __init first_drv_init(void)//驅動入口函數
{
    register_chrdev(111,"first_drv",&first_drv_fops);//註冊一個主設備號爲111的字符驅動設備 主設備號,設備名字,對應的結構體
    return 0;
}
/*模塊卸載函數,當經過rmmod命令卸載時,會被自動執行*/
static void __exit first_drv_exit(void)
{
    unregister_chrdev(111,"first_drv");
}
/* 這兩行指定驅動程序的初始化函數和卸載函數 */
module_init( first_drv_init);
module_exit(first_drv_exit);

MODULE_LICENSE("GPL");/*模塊許可證實,描述內核模塊的許可權限,必須*/

對應Makefilecode

KERN_DIR = /home/wang/linux-2.6.22.6// //內核路徑,根據實際狀況換成本身的內核路徑,嵌入式的換成嵌入式,PC機的指定PC機路徑

all:
    make -C $(KERN_DIR) M=`pwd` modules 

clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order

obj-m    += first_drv.o  //目標文件

最終會編譯獲得first_drv.ko文件,cp first_drv.ko "nfs文件系統" 使用insmod first_drv.ko加載模塊,使用cat /proc/devices或dmesg可查看

經常使用的幾種模塊操做:

insmod XXX.ko    加載指定模塊

lsmod                      列舉當前系統中的全部模塊

rmmod  XXX         卸載指定模塊(注意沒有.ko後綴)

dmesg                    當打印等級低於默認輸出等級時,採用此命令查看系統日誌

編寫對應的應用測試程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    int fd;
    int val = 1;
    fd = open("/dev/xyz", O_RDWR);
    if (fd < 0)
    {
        printf("can't open!\n");
    }
    write(fd, &val, 4);
    return 0;
}

使用arm-linux-gcc -o firstdrvtest firstdrvtest.c 編譯並拷貝到nfs文件系統

1建立設備節點 mknod /dev/xyz c 111 0 //建立主設備號爲111,刺設備號0的字符設備/dev/xyz

2 執行測試應用程序 ./firstdrvtest

打印輸出

first_drv_open

first_drv_write

上個驅動程序,須要本身指定主設備號,須要手動建立設備節點。那麼是否能夠自動建立?

1驅動:能夠自動分配主設備號,也能夠手動指定。

2應用 open("/dev/xxx") ->/dev/xxx怎麼來?

  a.手動建立 mknod /dev/xxx c major minor。

  b自動建立 udev。在文件系統根目錄下的sys目錄,當註冊一個驅動,會在該目錄生成一個的信息,而mdev自動根據這些信息建立節點。

因此驅動程序須要提供設備信息以創建設備節點。

示例2

static struct class *firstdrv_class;
static struct class_device    *firstdrv_class_dev;
int major;
static int _init first_drv_init(void)//驅動入口函數
{
    major = register_chrdev(0,"first_drv",&first_drv_fops);//註冊 返回的major就是自動分配的主設備號
    firstdrv_class = class_create(THIS_MODULE, "firstdrv");//創建一個類
    firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz");//類下邊創建一個設備 次設備號爲0 設備節點名爲xyz
    return 0;
}
void first_drv_exit(void)
{
    unregister_chrdev(major,"first_drv");
    class_device_unregister(firstdrv_class_dev);
    class_destroy(firstdrv_class);
}

mdev爲何會根據這些信息建立設備節點?由於在etc/init.d/rcS文件中有

echo /sbin/mdev > /proc/sys/kernel/hotplug  

當有設備註冊或卸載的時候,就會調用/proc/sys/kernel/hotplug

/**
 * class_device_create - creates a class device and registers it with sysfs
 * @cls: pointer to the struct class that this device should be registered to.
 * @parent: pointer to the parent struct class_device of this new device, if any.
 * @devt: the dev_t for the char device to be added.
 * @device: a pointer to a struct device that is assiociated with this class device.
 * @fmt: string for the class device's name
 *
 * This function can be used by char device classes.  A struct
 * class_device will be created in sysfs, registered to the specified
 * class.
 * A "dev" file will be created, showing the dev_t for the device, if
 * the dev_t is not 0,0.
 * If a pointer to a parent struct class_device is passed in, the newly
 * created struct class_device will be a child of that device in sysfs.
 * The pointer to the struct class_device will be returned from the
 * call.  Any further sysfs files that might be required can be created
 * using this pointer.
 *
 * Note: the struct class passed to this function must have previously
 * been created with a call to class_create().
 */
struct class_device *class_device_create(struct class *cls,
                     struct class_device *parent,
                     dev_t devt,
                     struct device *device,
                     const char *fmt, ...)

示例3

一個類的設備中,有多個設備,好比3個led燈

static struct class *leds_class;
static struct class_device    *leds_class_devs[3];
static unsigned long gpio_va;

static int s3c24xx_leds_open(struct inode *inode, struct file *file)
{
    int minor = MINOR(inode->i_rdev); 
    switch(minor)
    {
        case 0:
         {
              /*配置led1 gpio*/   
          }   break;
        case 1:
         {
              /*配置led2 gpio*/   
          }   break;
        case 2:
         {
              /*配置led3 gpio*/   
          }   break;
    }   
    return 0;
}
static ssize_t s3c24xx_leds_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    int minor = MINOR(file->f_dentry->d_inode->i_rdev);
     char val;
     copy_from_user(&val, buf, 1);
      switch(minor)
    {
        case 0:
         {
              /*根據val值控制led1 gpio*/   
          }   break;
        case 1:
         {
              /*根據val值控制led2 gpio*/   
          }   break;
        case 2:
         {
              /*根據val值控制led3 gpio*/   
          }   break;
    }   
}
static struct file_operations s3c24xx_leds_fops = {
    .owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動建立的__this_module變量 */
    .open   =   s3c24xx_leds_open,     
    .read    =    s3c24xx_leds_read,       
    .write    =    s3c24xx_leds_write,       
};
/*
 * 執行insmod命令時就會調用這個函數 
 */
static int __init s3c24xx_leds_init(void)
{
        int ret;
    int minor = 0;
        gpio_va = ioremap(0x56000000, 0x100000);//IO重映射 申請IM空間
    /* 註冊字符設備
     * 參數爲主設備號、設備名字、file_operations結構;
     * 這樣,主設備號就和具體的file_operations結構聯繫起來了,
     * 操做主設備爲LED_MAJOR的設備文件時,就會調用s3c24xx_leds_fops中的相關成員函數
     * LED_MAJOR能夠設爲0,表示由內核自動分配主設備號
     */
    ret = register_chrdev(231, 「leds」, &s3c24xx_leds_fops);
    leds_class = class_create(THIS_MODULE, "leds");
for (minor = 0; minor < 3; minor++)
    {
        leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);
    }
}
/*
 * 執行rmmod命令時就會調用這個函數 
 */
static void __exit s3c24xx_leds_exit(void)
{
    int minor;
    /* 卸載驅動程序 */
    unregister_chrdev(LED_MAJOR, DEVICE_NAME);

    for (minor = 0; minor < 3; minor++)
    {
        class_device_unregister(leds_class_devs[minor]);
    }
    class_destroy(leds_class);
        iounmap(gpio_va);
}
/* 這兩行指定驅動程序的初始化函數和卸載函數 */
module_init(s3c24xx_leds_init);
module_exit(s3c24xx_leds_exit);

MODULE_LICENSE("GPL");
相關文章
相關標籤/搜索