1. 應用場景
比如我們的設備上有很多一樣的usb接口,這些usb接口都需要有驅動才能工作,那么是每個usb都一套單獨的驅動程序么?顯然不是的,這些usb接口屬于同一類設備,用戶對他們的操作方法完全一致,只不過不是同一個設備,所以他們可以復用同一套驅動代碼,在代碼中去判斷用戶要操作哪個設備,然后去open/read/write這個設備。
2. 如何區分不同的設備
前面說過,每個設備都有一個唯一的標識符–設備號,那么對于同一類設備,它們的主設備號是一樣的,次設備號是不一樣的,用來區分它們,當用戶想要操作哪個具體的設備,就會打開這個設備對應的設備文件(inode結構體),并自動在內核中創建對應的file結構體,這個file結構體中就保存了用戶操作的所有信息,最終會傳給我們的內核驅動,驅動再根據這個file結構體和inode結構體來判斷用戶具體要操作的哪個設備,然后去read/write這個具體的設備。
案例:
?
hanp@hanp:/dev/usb$ ls -l crw------- 1 root root 180, 0 3月 11 17:29 hiddev0 crw-------?1?root?root?180,?1??3月?11?17:29?hiddev1
?
我的主機下面的兩個usb設備,他們共用了一套usb驅動,但是他們的設備號是不一樣的(180,0)和(180,1),主設備號都是180表示都屬于同一類設備(usb設備),次設備號分別是0和1,表示這是兩個不同的設備。
3. 代碼實現
?
#include#include #include #include #include #include #define NUM_OF_DEVICES 2 int major = 255; /* 兩個設備,所以有兩套結構體 */ /* 設備0對應的設備結構體是hello_dev[0], 設備1對應的設備結構體是hello_dev[1] */ struct hello_device { dev_t devno; struct cdev cdev; char data[128]; char name[16]; }hello_dev[NUM_OF_DEVICES]; struct class * hello_class; const char DEVNAME[] = "hello_device"; int hello_open(struct inode * ip, struct file * fp) { printk("%s : %d ", __func__, __LINE__); /* 獲取用戶打開的設備對應的設備結構體 hello_dev[0] 或者 hello_dev[1] */ struct hello_device * dev = container_of(ip->i_cdev, struct hello_device, cdev); /* open的時候,通過container_of能夠獲取到用戶要打開的那個設備的設備結構體,所有需要把這個結構體通過file指針的 * private_data參數傳遞給read/write */ fp->private_data = dev; /* 一般用來做初始化設備的操作 */ /* ... */ return 0; } int hello_close(struct inode * ip, struct file * fp) { printk("%s : %d ", __func__, __LINE__); /* 一般用來做和open相反的操作,open申請資源,close釋放資源 */ /* ... */ return 0; } ssize_t hello_read(struct file * fp, char __user * buf, size_t count, loff_t * loff) { int ret; /* 通過file指針,獲取到用戶要操作的設備對應的設備結構體 */ struct hello_device * dev = fp->private_data; /* 將用戶需要的數據從內核空間copy到用戶空間(buf) */ printk("%s : %d ", __func__, __LINE__); if (count <=0 || count > 128) count = 128; if ((ret = copy_to_user(buf, dev->data, count))) { printk("copy_to_user err "); return -1; } return count; } ssize_t hello_write(struct file * fp, const char __user * buf, size_t count, loff_t * loff) { int ret; struct hello_device * dev = fp->private_data; /* 將用戶需要的數據從內核空間copy到用戶空間(buf) */ printk("%s : %d ", __func__, __LINE__); if (count <=0 || count > 128) count = 128; if ((ret = copy_from_user(dev->data, buf, count))) { printk("copy_from_user err "); return -1; } return count; } /* 2. 分配file_operations結構體 */ struct file_operations hello_fops = { .owner = THIS_MODULE, .open = hello_open, .release = hello_close, .read = hello_read, .write = hello_write }; struct cdev cdev; static int hello_init(void) { int i; printk("%s : %d ", __func__, __LINE__); /* 1. 生成并注冊兩個設備的設備號 */ /* 3. 分配、設置、注冊兩套cdev結構體 */ for (i = 0; i < NUM_OF_DEVICES; i++) { hello_dev[i].devno = MKDEV(major, i); sprintf(hello_dev[i].name, "%s%d", DEVNAME, i); register_chrdev_region(hello_dev[i].devno, 1, hello_dev[i].name); hello_dev[i].cdev.owner = THIS_MODULE; cdev_add(&hello_dev[i].cdev, hello_dev[i].devno, 1); cdev_init(&hello_dev[i].cdev, &hello_fops); /* 初始化兩個設備各自的存儲空間 */ sprintf(hello_dev[i].data, "Hi, I am hello device %d", i); } /* 在/sys/class目錄下創建hello類,并在這個類下面創建hello_device0和hello_device1 */ hello_class = class_create(THIS_MODULE, DEVNAME); for (i = 0; i < NUM_OF_DEVICES; i++) { device_create(hello_class, NULL, hello_dev[i].devno, NULL, "%s%d", DEVNAME, i); printk("success! "); } return 0; } static void hello_exit(void) { int i; printk("%s : %d ", __func__, __LINE__); /* 釋放資源 */ for (i = 0; i < NUM_OF_DEVICES; i++) { device_destroy(hello_class, hello_dev[i].devno); cdev_del(&hello_dev[i].cdev); unregister_chrdev_region(hello_dev[i].devno, 1); } class_destroy(hello_class); } MODULE_LICENSE("GPL"); module_init(hello_init); module_exit(hello_exit);
?
解釋:
?
container_of: /** * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * */ #define container_of(ptr, type, member)
?
功能:根據結構體中某個成員的地址,從而獲取到整個結構體的首地址
@ptr: 已知結構體成員的地址
@type: 要獲取的結構體的類型
@member: 已知結構體成員的名字
我們用到的實例解析:
struct hello_device * dev = container_of(ip->i_cdev, struct hello_device, cdev);
文章最后的圖解和上篇文章中我講到file結構體和inode結構體的關系,其中inode結構體和文件系統下的文件是一一對應的關系,里面保存了這個字符設備對應的cdev結構體:
struct cdev * i_cdev,而這個cdev結構體又包含在設備結構體hello_device中,其中hello_dev[0]中包含的是設備0的cdev,hello_dev[1]中包含的是設備1的cdev,那么
container_of函數就可以根據這個cdev來判斷用戶打開的是hello_dev[0]還是hello_dev[1]并獲取到地址。
編譯安裝驅動:
sudo insmod hello.ko
?
hanp@hanp:/dev$ ls hello_device* hello_device0??hello_device1
hanp@hanp:/dev$ cat /proc/devices | grep hello 255 hello_device0 255?hello_device1
?
可以看到在/proc/devices下注冊了兩個設備hello_device0和hello_device1,這兩個設備的主設備一樣都是255,但是次設備號不一樣(cat /dev/hello_deviceX可以查看次設備號)。
4. 寫應用程序進行測試 app.c
?
#include#include #include #include #include #include int main(char argc, char * argv[]) { int fd; int ret; char buf[64]; if (argc != 2) { printf("Usage: %s ", argv[0]); return -1; } fd = open(argv[1], O_RDWR); if (fd < 0) { perror("fail to open file"); return -1; } /* read data */ ret = read(fd, buf, sizeof(buf)); if (ret < 0) { printf("read err!"); return -1; } printf("buf = %s ", buf); /* write data */ strcpy(buf, "write data from app!"); ret = write(fd, buf, sizeof(buf)); if (ret < 0) { printf("write err!"); return -1; } read(fd, buf, sizeof(buf)); printf("buf = %s ", buf); close(fd); return 0; }
?
測試:
?
$ gcc app.c $ sudo ./a.out /dev/hello_device0 buf = Hi, I am hello device 0 buf = write data from app! $ sudo ./a.out /dev/hello_device1 buf = Hi, I am hello device 1 buf?=?write?data?from?app!
?
審核編輯:湯梓紅
評論
查看更多