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
//第一個引數是裝置名字
//裝置的型別
//主裝置號
//次裝置號
- 執行測試程式:
./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");