1. 程式人生 > >2---linux字元裝置進階篇

2---linux字元裝置進階篇

概要:上一篇我們編寫了一個簡單的驅動程式,似乎有點索然無味,其實我也是這樣覺得的,所以這篇我們將加大力度。

看了上一篇的字元裝置似乎覺得很簡單,事實不然。

如何註冊字元裝置

先來看幾個函式:
int register_chrdev_region(dev_t dev_id, unsigned count, const char *name)

靜態註冊字元裝置,自己指定主裝置號。什麼叫靜態,就是你自己靜靜地動手註冊的這個狀態,就叫靜態,自己動手註冊,累人。
dev_id:指定了裝置號的起始地址,我們用MKDEV(major,minor)來指定,起始主裝置號是major,起始次裝置號為minor
count: 你想一次性註冊次裝置號的個數
*name:名字隨意 ,比如”sun_xiao_chuan“
注意:判斷返回值,小於0就說明註冊失敗了。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)

動態註冊字元裝置,核心幫你指定主裝置。什麼叫動態,就是核心動起來幫你註冊的這個狀態,就叫動態,我還是很喜歡別人自己動的,咳咳,有點跑題
*dev:核心幫你分配後總得告訴你,它的主次裝置號吧,它分配完之後就放進dev裡面
baseminor:次裝置號的起始號
count:你想一次性註冊次裝置號的個數
name:名字隨意,比如"dai_dai_da_shi_xiong"
注意:判斷返回值,小於0就說明註冊失敗了。

對於這個兩個函式看實際情況使用,又或者說你喜歡自己動還是別人動,比如我喜歡別人動,那就這麼註冊字元裝置:

 alloc_chrdev_region(&devid, 0, 3, "hello");
 //那麼我就讓核心幫我分配字元裝置,分配完之後總得告訴我主裝置吧?
 major = MAJOR(devid)
 //使用這個巨集定義來獲取主裝置

有始有終,你在入口函式註冊了我,我就在出口函式登出你

unregister_chrdev_region(MKDEV(major, 0), 3);

在出口函式,使用這個函式可以把它登出掉

字元裝置的精髓:file_operations:

file_operations到底是這什麼東西?它賦予了字元裝置生命,又或者說它這是字元裝置的技能。你建立的字元裝置可以有什麼技能,這個完全由你來決定,是不是覺得自己就像上帝一樣。
比如說我想我的字元裝置能進行寫操作,那就要編寫屬於這個字元裝置的寫操作函式, 比如說我想讓我的字元裝置能幫我找小電影,那就編寫一個…好吧,這裡面沒有能找小電影的函式。

file_operations結構體

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*dir_notify)(struct file *filp, unsigned long arg);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
};

根據自己需求來寫file_operafile,比如:

static struct const file_operation hello_fops = {
	.owner = THIS_MODULE, //這個fops只屬於我這個字元裝置
	.open = hello_open, //每當有人打開了我這個字元裝置就會執行 hello_open,或者說你來開啟我的字元裝置我就會找小電影
};

那麼完成了自己的fops,怎麼告訴核心這個fops是我字元裝置的fops:

我們需要使用一個結構體cdev:

struct cdev {
       struct kobject    kobj;                   // 內嵌的kobject物件 
       struct module   *owner;                   //所屬模組
       const struct file_operations  *ops;     //操作方法結構體
       struct list_head  list;            //與 cdev 對應的字元裝置檔案的 inode->i_devices 的連結串列頭
       dev_t dev;                    //起始裝置編號,可以通過MAJOR(),MINOR()來提取主次裝置號
       unsigned int count;                //連續註冊的次裝置號個數
}

使用cdev之前進行初始化:

static struct cdev hello_dev;
void cdev_init(&hello_dev, &hello_fops);

cdev:這裡裝了這個字元裝置基本資訊。核心就像xx局一樣,你要去xx局弄一張身份證,你交一大堆材料,xx局確認你是個人之後,給你一張身份證,從此中國又多了一個人。這個身份證就是cdev。
file_operations:這裡就是我們剛剛建立的那個能找小電影的fops

回想一下,我們之前註冊了字元裝置,但好像核心並沒有把我的字元裝置放進核心吧
cdev_add(&hello_cdev, devid,2);

這一步就是把我的cdev告訴了核心,或者說就是把我這個字元裝置的主次裝置、fops,告訴了核心。

void cdev_del(&hello_cdev);

在出口函式,使用這個函式刪除這個結構體

到這一步起始我們的驅動程式就能用起來了:

我這篇文章的例子用到的是動態註冊字元裝置,主裝置號我們自己要動手拿出來。然後我還是要談一下fops的歸屬問題,我自己寫的這個fops有誰能用,假設我們動態註冊的major(主裝置號)為255,minor(次裝置)是從1開始的,次裝置號的個數為3,那麼major為255,minor從1到3的這些字元裝置都擁有我寫的fops

我們還是老樣子
1.編寫makefile
2.使用make編譯
3.把ko檔案拷貝到開發板上
4.insmod hello.ko

我們的驅動就被載入進核心了,我們來編寫一個測試程式

****hello_test.c****

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char **argv)
{

  int fd;
  fd=open(argv[1],O_RDWR); //我們的fops只有.open函式,所以我們就只打開/dev/hello
  if(fd<0)
      printf("can't open %s \n",argv[1]);
  else
      printf("can open %s \n",argv[1]);
  return 0;
}

5.編譯測試程式:arm-linux-gcc
6.拷貝到開發板
7.執行測試程式

 ./hello_test   /dev/hello

這時候百分百是列印:can’t open /dev/hello,我們開啟失敗了,為什麼?
答:因為在/dev目錄下根本沒有hello這個字元裝置節點,所以我們要建立這個檔案。

8 建立裝置節點之前我們先檢視一下核心分配的主裝置號是多少
使用命令:cat /proc/device 得到major為255

9.使用mknod命令裝置節點

mknod  /dev/hello c  255  1  
mknod  /dev/hello1 c  255 2
mknod  /dev/hello2 c  255 3
//第一個引數是裝置名字
//裝置的型別
//主裝置號
//次裝置號
  1. 執行測試程式:
 ./hello_test   /dev/hello1
 ./hello_test   /dev/hello2
 ./hello_test   /dev/hello3

輸出:

can open /dev/hello1
can open /dev/hello2
can open /dev/hello3

自動建立裝置節點

我們這樣自己這樣一個一個地建立裝置節點真的很累,我說過我喜歡別人來動,所以核心有提供自動建立裝置節點的函式。

static struct class *cls;
cls = class_create(THIS_MODULE, "hello");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0");
class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1");
class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2");

建立名為“hello”的這個類
這個類下面有 “hello0” ,“hello1” ,“hello2”,這些裝置節點

      	class_device_destroy(cls, MKDEV(major, 0));
        class_device_destroy(cls, MKDEV(major, 1));
        class_device_destroy(cls, MKDEV(major, 2));
        class_destroy(cls);

在出口函式,使用這些函式來解除安裝裝置節點和類

重新make ,拷貝到開發板,載入驅動程式:

insmod hello.ko

檢視/dev目錄下有沒有名為“hello*”的裝置節點

ls -l /dev/hello*

執行測試程式:

 ./hello_test   /dev/hello1
 ./hello_test   /dev/hello2
 ./hello_test   /dev/hello3

一步成功,完全不需要自己手動建立
到這裡字元裝置被我載入進核心,並且測試程式能呼叫到fops裡面的hello_open()函式

*****hello.c*****
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/list.h>
#include <linux/cdev.h>

static int major;

static int hello_open(struct inode *inode, struct file *file)
{
        printk("hello_open\n");
        return 0;
}

static struct file_operations hello_fops = {
        .owner = THIS_MODULE,
        .open  = hello_open,  //自己編寫的open()函式
};
static struct cdev hello_cdev;
static struct class *cls;

static int hello_init(void)//入口函式
{
        dev_t devid;
        
        major = register_chrdev(0, "hello", &hello_fops);
        if (major) //靜態註冊
        {
              devid = MKDEV(major, 0);
             register_chrdev_region(devid, 3, "hello");  
        } 
        else  //動態註冊
        {
              alloc_chrdev_region(&devid, 0, 3, "hello"); 
              major = MAJOR(devid);            				  
        }

        cdev_init(&hello_cdev, &hello_fops); //初始化cdev結構體
        cdev_add(&hello_cdev, devid, HELLO_CNT); //向核心新增cdev結構體


        cls = class_create(THIS_MODULE, "hello");  //自動建立裝置節點
		class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */
        class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */
        class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */


        return 0;
        
 }
static void hello_exit(void) //出口函式
{
        class_device_destroy(cls, MKDEV(major, 0));
        class_device_destroy(cls, MKDEV(major, 1));
        class_device_destroy(cls, MKDEV(major, 2));
        class_destroy(cls);

        cdev_del(&hello_cdev);
        unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);
}

module_init(hello_init);
module_exit(hello_exit);


MODULE_LICENSE("GPL");