天天看點

Linux自己編寫一個字元裝置驅動的執行個體(+代碼)字元裝置驅動

字元裝置驅動

Linux字元裝置提供連續的資料流,應用程式可以順序讀取,通常不支援随機存取。相反,此類裝置支援按位元組/字元來讀寫裝置。舉例來說,鍵盤,序列槽,數據機都是典型的字元裝置。

裝置分類

linux系統将裝置分為3類:字元裝置、塊裝置、網絡裝置。

  • 字元裝置:是指隻能一個位元組一個位元組讀寫的裝置,不能随機讀取裝置記憶體中的某一資料,讀取資料需要按照先後資料。字元裝置是面向流的裝置,常見的字元裝置有滑鼠、鍵盤、序列槽、控制台和LED裝置等。
  • 塊裝置:是指可以從裝置的任意位置讀取一定長度資料的裝置。塊裝置包括硬碟、磁盤、U盤和SD卡等。
  • 網絡裝置:網絡裝置比較特殊,不在是對檔案進行操作,而是由專門的網絡接口來實作。應用程式不能直接通路網絡裝置驅動程式。在/dev目錄下也沒有檔案來表示網絡裝置。

每一個字元裝置或塊裝置都在/dev目錄下對應一個裝置檔案。linux使用者程式通過裝置檔案(或稱裝置節點)來使用驅動程式操作字元裝置和塊裝置。

相關函數調用

struct cdev 描述字元裝置的結構體

struct cdev {
	struct kobject kobj;//内嵌的核心對象.  
	struct module *owner;//該字元裝置所在的核心子產品(所有者)的對象指針,一般為THIS_MODULE主要用于子產品計數  
	const struct file_operations *ops;//該結構描述了字元裝置所能實作的操作集(打開、關閉、讀/寫、...),是極為關鍵的一個結構體
	struct list_head list;//用來将已經向核心注冊的所有字元裝置形成連結清單
	dev_t dev;//字元裝置的裝置号,由主裝置号和次裝置号構成(如果是一次申請多個裝置号,此裝置号為第一個)
	unsigned int count;//隸屬于同一主裝置号的次裝置号的個數
};
           

cdev_alloc 動态申請(構造)cdev記憶體(裝置對象)

struct cdev *cdev_alloc(void);  

struct cdev *cdev_alloc(void)
{
	struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
    //為裝置申請核心記憶體,并對申請到的記憶體内容清零,GFP_KERNEL —— 正常配置設定記憶體。
	if (p) {
		INIT_LIST_HEAD(&p->list);//初始化連結清單
		kobject_init(&p->kobj, &ktype_cdev_dynamic);//初始化核心對象
	}
	return p;
}

           

成功的話傳回值為cdev對象首位址

cdev_init 初始化cdev的成員,并建立cdev和file_operations之間關聯起來

void cdev_init(struct cdev *p, const struct file_operations *p);
/* 參數:
    struct cdev *p - 被初始化的 cdev對象
    const struct file_operations *fops - 字元裝置操作方法集 */
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);
    //将為cdev設定的記憶體空間初始化,全設為0.
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops;//建立cdev和file_operations之間的關系
}
           

cdev_add 注冊cdev裝置對象(添加到系統字元裝置清單中)

int cdev_add(struct cdev *p, dev_t dev, unsigned count);
/* 參數:
    struct cdev *p - 被注冊的cdev對象
    dev_t dev - 裝置的第一個裝置号
    unsigned - 這個裝置連續的次裝置号數量
   傳回值:
    成功:0
    失敗:負數(絕對值是錯誤碼)*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	p->dev = dev;
	p->count = count;//填充cdev結構體
	return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);//添加裝置号到系統字元裝置清單中
}

           

cdev_del 将cdev對象從系統中移除(登出 )

void cdev_del(struct cdev *p);
/*參數: 
    struct cdev *p - 要移除的cdev對象 */
void cdev_del(struct cdev *p)
{
	cdev_unmap(p->dev, p->count);
	kobject_put(&p->kobj);//減少核心對象的引用計數
}

static void cdev_unmap(dev_t dev, unsigned count)
{
	kobj_unmap(cdev_map, dev, count);//将裝置号從系統字元裝置清單中删除
}
           

cdev_put 釋放cdev記憶體

void cdev_put(struct cdev *p);
/*參數:
    struct cdev *p - 要移除的cdev對象 */
void cdev_put(struct cdev *p)
{
	if (p) {
		struct module *owner = p->owner;
		kobject_put(&p->kobj);
		module_put(owner);//子產品解除安裝
	}
}
           

裝置号申請/釋放

一個字元裝置或塊裝置都有一個主裝置号和一個次裝置号。主裝置号用來辨別與裝置檔案相連的驅動程式,用來反映裝置類型。次裝置号被驅動程式用來辨識操作的是哪個裝置,用來區分同類型的裝置。linux核心中,裝置号用dev_t來描述:

typedef u_long dev_t;  // 在32位機中是4個位元組,高12位表示主裝置号,低20位表示次裝置号。 
           

實作dev_t的宏

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
    #define MINORBITS	20
    //dev右移20位得到主裝置号,即為高12位

	#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
	#define MINORMASK	((1U << MINORBITS) - 1)
	//MINOR宏将dev_t的高12位清零,得到次裝置号。
	
           

裝置号申請的方法

靜态:

int register_chrdev_region(dev_t from, unsigned count, const char *name);
/*功能:申請使用從from開始的count 個裝置号(主裝置号不變,次裝置号增加)*/
           

靜态申請相對較簡單,但是一旦驅動被廣泛使用,這個随機標明的主裝置号可能會導緻裝置号沖突,而使驅動程式無法注冊。

動态:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
/*功能:請求核心動态配置設定count個裝置号,且次裝置号從baseminor開始。*/
           

動态申請簡單,易于驅動推廣,但是無法在安裝驅動前建立裝置檔案(因為安裝前還沒有配置設定到主裝置号)。

釋放裝置号

編寫簡單的字元裝置驅動

device_drive.c

# include <linux/module.h>
# include <linux/fs.h>
# include <linux/uaccess.h>
# include <linux/init.h>
# include <linux/cdev.h>
# define DEMO_NAME "my_demo_dev"

static dev_t dev;
static struct cdev *demo_cdev;
static signed count = 1;
//打開操作
static int demodrv_open(struct inode *inode, struct file *file)
{
	int major = MAJOR(inode->i_rdev);
   
	int minor = MINOR(inode->i_rdev);

printk("%s: major=%d, minor=%d\n",__func__,major,minor);
return 0;
}

//讀操作
static ssize_t demodrv_read(struct file *file, char __user *buf,size_t lbuf,loff_t *ppos)
{
	printk("%s enter\n",__func__);
	

return 0;

}

//寫操作
static ssize_t demodrv_write(struct file *file, const char __user *buf,size_t count,loff_t *f_pos)
{
	printk("%s enter\n",__func__);
	

return 0;

}

//實作裝置操作 
static const struct file_operations demodrv_fops = {
	.owner = THIS_MODULE,
	.open = demodrv_open,
	.read = demodrv_read,
	.write = demodrv_write
};


static int __init simple_char_init(void)
{
	int ret;

ret = alloc_chrdev_region(&dev,0,count,DEMO_NAME);
    /*
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
    功能:請求核心動态配置設定count個裝置号,且次裝置号從baseminor開始。
    */
if(ret)
{
	printk("failed to allocate char device region\n");
	return ret;
}
demo_cdev = cdev_alloc();
if(!demo_cdev) 
{
	printk("cdev_alloc failed\n");
	goto unregister_chrdev;
}

cdev_init(demo_cdev,&demodrv_fops);

ret = cdev_add(demo_cdev,dev,count);
if(ret)
{
	printk("cdev_add failed\n");
	goto cdev_fail;
}

printk("successed register char device: %s\n",DEMO_NAME);
printk("Major number = %d,minor number = %d\n",MAJOR(dev),MINOR(dev));

return 0;

cdev_fail:
	cdev_del(demo_cdev);

unregister_chrdev:
	unregister_chrdev_region(dev,count);

return ret;

} 


static void __exit simple_char_exit(void)
{
	printk("removing device\n");

if(demo_cdev)
	cdev_del(demo_cdev);

unregister_chrdev_region(dev,count);

}

module_init(simple_char_init);
module_exit(simple_char_exit);

MODULE_LICENSE("GPL");
           

Makefile

#Makefile檔案注意:假如前面的.c檔案起名為first.c,那麼這裡的Makefile檔案中的.o文
#件就要起名為first.o    隻有root使用者才能加載和解除安裝子產品
obj-m:=device_drive.o                          #産生device_drive子產品的目标檔案
#目标檔案  檔案  要與子產品名字相同
CURRENT_PATH:=$(shell pwd)             #子產品所在的目前路徑
LINUX_KERNEL:=$(shell uname -r)        #linux核心代碼的目前版本
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)

all:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules    #編譯子產品
#[Tab]              核心的路徑       目前目錄編譯完放哪  表明編譯的是核心子產品

clean:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean      #清理子產品
           

test.c

# include <stdio.h>

# include <fcntl.h>

# include <unistd.h>

# define DEMO_DEV_NAME "/dev/demo_drv"

int main() 
{
	char buffer[64];
	int fd;

fd = open(DEMO_DEV_NAME,O_RDONLY);
if(fd<0) 
{
	printf("open device %s failed\n",DEMO_DEV_NAME);
	return -1;
}

read(fd,buffer,64);
close(fd);

return 0;

}