A. linux用mknod怎麼創建設備怎麼用
首先要明白什麼是設備文件,簡單的我們說,操作系統與外部設備(入磁碟驅動器,列印機,modern,終端
等等)都是通過設備文件來進行通信的,在Unix/Linux系統與外部設備通訊之前,這個設備必須首先要有一個設備文件,設備文件均放在/dev目錄下。
一般情況下在安裝系統的時候系統自動創建了很多已檢測到的設備的設備文件,但有時候我們也需要自己手動創建,命令行生成設備文件的方式有
insf,mksf,mknod等等
根據mknod命令的使用參數來看【mknod
Name
{
b
|
c
}
Major
Minor
】,使用mknod之前,至少要明白以下幾點:
設備文件類型:分為塊設備和字元設備。ls
-l
/dev
結果顯示第一個欄位有b***
和
c****,這里即標識了塊設備和字元設備。
字元設備文件----字元設備文件傳送數據給設備的時候,一次傳送一個字元,終端,列印機,繪圖儀,modern等設備都經過字元設備文件傳送數據
塊設備---系統通過塊設備文件存取一個設備的時候,先從內存中的buffer中讀或寫數據,而不是直接傳送數據到物理磁碟,這種方式能有效的提高磁碟和CD-ROMS的I/O性能。磁碟和CD-ROMS即可以使用字元設備文件也可使用塊設備文件。
主號和次號
主號:當在任意目錄使用ls
-l
時,結果的第5個欄位就是主號,設備主號代表了這個設備使用的是哪個設備驅動程序
次號:次號是一個24位的十六進制數字,定義了設個設備在系統中的物理的位置
就拿我們常用的創建卷組來看:
先來看看mknod
命令,如果該設備文件你想放在一個特定的文件夾下當然就先創建文件夾
mknod
設備文件名[/dev/xyz]
b/c
主號
次號
{
mkdir
/dev/vg01
mknod
/dev/vg01/group
c
64
0X010000}
創建之後,就可以使用你想要創建的設備對於德創建命令了,如我現在的卷組的創建命令:
vgcreate
/dev/vg01
/dev/dsk/c*t*d*
一直進行下去,之後的步驟根據不同的設備而不盡相同。
B. linux 設備的含義
inux 中的設備有2種類型:字元設備(無緩沖且只能順序存取)、塊設備(有緩沖且可以隨機存取)。這些設備中,有些設備是對實際存在的物理硬體的抽象,而有些設備則是內核自身提供的功能(不依賴於特定的物理硬體,又稱為"虛擬設備")。每個設備在 /dev 目錄下都有一個對應的文件(節點)。
常見設備及相應/dev/下的文件名:
/dev/usb/hiddev0 | /dev/usb/hiddev1 --- USB 字元設備(滑鼠/鍵盤/游戲桿/手寫版等人操作計算機的設備)
/dev/uba | /dev/ubb --- USB 塊設備(U盤之類)
/dev/sda | /dev/sdb --- 第n個 SCSI 磁碟(整個磁碟)
/dev/hda | /dev/hdb --- 第n個 IDE 磁碟(整個磁碟)
/dev/sdc1 | /dev/sdc2 --- 第3個 SCSI 磁碟的第n個 分區 Linux/i386來說,分區1-4是主分區,5-15是邏輯分區。
/dev/scd0 | /dev/scd1 --- 第n個 SCSI CD-ROM
/dev/tty0 | /dev/tty1 --- 當前虛擬控制台、第n個虛擬控制台 TTY(終端)設備
Linux系統Mount點:mount的時候,mount的目標文件夾/media/cdrom要手動創建
mount /dev/cdrom /media/cdrom
C. 在Linux內核中,注冊字元設備驅動程序的函數是
字元設備驅動程序框架 1、寫出open、write函數 2、告訴內核 1)、定義一個struct file_operations結構並填充好 static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_mole變數 */ .open = first_drv_open, .write = first_drv_write, }; 2)、把struct file_operations結構體告訴內核 major = register_chrdev(0, "first_drv", &first_drv_fops); // 注冊, 告訴內核相關參數:第一個,設備號,0自動分配主設備號,否則為主設備號0-255 第二個:設備名第二個:struct file_operations結構體 4)、register_chrdev由誰調用(入口函數調用) static int first_drv_init(void) 5)、入口函數須使用內核宏來修飾 mole_init(first_drv_init); mole_init會定義一個結構體,這個結構體裡面有一個函數指針指向first_drv_init這個函數,當我們載入或安裝一個驅動時,內核會自動找到這個結構體,然後調用裡面的函數指針,這個函數指針指向first_drv_init這個函數,first_drv_init這個函數就是把struct file_operations結構體告訴內核 6)、有入口函數就有出口函數 mole_exit(first_drv_exit); 最後加上協議 MODULE_LICENSE("GPL"); 3、mdev根據系統信息自動創建設備節點: 每次寫驅動都要手動創建設備文件過於麻煩,使用設備管理文件系統則方便很多。在2.6的內核以前一直使用的是devfs,但是它存在許多缺陷。它創建了大量的設備文件,其實這些設備更本不存在。而且設備與設備文件的映射具有不確定性,比如U盤即可能對應sda,又可能對應sdb。沒有足夠的主/輔設備號。2.6之後的內核引入了sysfs文件系統,它掛載在/sys上,配合udev使用,可以很好的完成devfs的功能,並彌補了那些缺點。(這里說一下,當今內核已經使用netlink了)。 udev是用戶空間的一個應用程序,在嵌入式中用的是mdev,mdev在busybox中。mdev是udev的精簡版。首先在busybox中添加支持mdev的選項: Linux System Utilities ---> [*] mdev [*] Support /etc/mdev.conf [*] Support subdirs/symlinks [*] Support regular expressions substitutions when renaming device [*] Support command execution at device addition/removal 然後修改/etc/init.d/rcS: echo /sbin/mdev > /proc/sys/kernel/hotplug /sbin/mdev -s 執行mdev -s :以『-s』為參數調用位於 /sbin目錄寫的mdev(其實是個鏈接,作用是傳遞參數給/bin目錄下的busybox程序並調用它),mdev掃描 /sys/class 和 /sys/block 中所有的類設備目錄,如果在目錄中含有名為「dev」的文件,且文件中包含的是設備號,則mdev就利用這些信息為這個設備在/dev 下創建設備節點文件。一般只在啟動時才執行一次 「mdev -s」。熱插拔事件:由於啟動時運行了命 令:echo /sbin/mdev > /proc/sys/kernel/hotplug ,那麼當有熱插拔事件產生時,內核就會調用位於 /sbin目錄的mdev。這時mdev通過環境變數中的 ACTION 和 DEVPATH,來確定此次熱插拔事件的動作以及影響了/sys中的那個目錄。接著會看看這個目錄中是否「dev」的屬性文件,如果有就利用這些信息為 這個設備在/dev 下創建設備節點文件重新打包文件系統,這樣/sys目錄,/dev目錄就有東西了下面是create_class的原型: #define class_create(owner, name) / ({ / static struct lock_class_key __key; / __class_create(owner, name, &__key); / }) extern struct class * __must_check __class_create(struct mole *owner, const char *name, struct lock_class_key *key); class_destroy的原型如下: extern void class_destroy(struct class *cls); device_create的原型如下: extern struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...) __attribute__((format(printf, 5, 6))); device_destroy的原型如下: extern void device_destroy(struct class *cls, dev_t devt); 具體使用如下,可參考後面的實例: static struct class *firstdrv_class; static struct class_device *firstdrv_class_dev; firstdrv_class = class_create(THIS_MODULE, "firstdrv"); firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */ class_device_unregister(firstdrv_class_dev); class_destroy(firstdrv_class); 下面再來看一下應用程序如何找到這個結構體的在應用程序中我們使用open打開一個設備:如:open(/dev/xxx, O_RDWR); xxx有一個屬性,如字元設備為c,後面為讀寫許可權,還有主設備名、次設備名,我們注冊時 通過register_chrdev(0, "first_drv", &first_drv_fops)(有主設備號,設備名,struct file_operations結構體)將first_drv_fops結構體注冊到內核數組chrdev中去的,結構體中有open,write函數,那麼應用程序如何找到它的,事實上是根據打開的這個文件的屬性中的設備類型及主設備號在內核數組chrdev裡面找到我們注冊的first_drv_fops,實例代碼: #include #include #include #include #include #include #include #include #include #include static struct class *firstdrv_class; static struct class_device *firstdrv_class_dev; volatile unsigned long *gpfcon = NULL; volatile unsigned long *gpfdat = NULL; static int first_drv_open(struct inode *inode, struct file *file) { //printk("first_drv_open\n"); /* 配置GPF4,5,6為輸出 */ *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2))); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; //printk("first_drv_write\n"); _from_user(&val, buf, count); // _to_user(); if (val == 1) { // 點燈 *gpfdat &= ~((1<<4) | (1<<5) | (1<<6)); } else { // 滅燈 *gpfdat |= (1<<4) | (1<<5) | (1<<6); } return 0; } static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_mole變數 */ .open = first_drv_open, .write = first_drv_write, }; int major; static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fops); // 注冊, 告訴內核 firstdrv_class = class_create(THIS_MODULE, "firstdrv"); firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */ gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); gpfdat = gpfcon + 1; return 0; } static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); // 卸載 class_device_unregister(firstdrv_class_dev); class_destroy(firstdrv_class); iounmap(gpfcon); } mole_init(first_drv_init); mole_exit(first_drv_exit); MODULE_LICENSE("GPL"); 編譯用Makefile文件 KERN_DIR = /work/system/linux-2.6.22.6 all: make -C $(KERN_DIR) M=`pwd` moles clean: make -C $(KERN_DIR) M=`pwd` moles clean rm -rf moles.order obj-m += first_drv.o 測試程序: #include #include #include #include /* firstdrvtest on * firstdrvtest off */ 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"); } if (argc != 2) { printf("Usage :\n"); printf("%s \n", argv[0]); return 0; } if (strcmp(argv[1], "on") == 0) { val = 1; } else { val = 0; } write(fd, &val, 4); return 0; }
D. linux設備驅動第三篇:如何寫一個簡單的字元設
#include<iostream>
#include<cstring>
using namespace std;
class string
{
private:
char *str;
public:
string(char *s);
~string(){delete str;};
int getlen(){return strlen(str)+1;}
char *get(){return str;}
void print();
};
string::string(char *s)
{
str=new char[strlen(s)+1];
strcpy(str,s);
cout<<"constructing string"<<endl;
}
void string::print()
{
cout<<"原數組為:"<<str<<endl;
}
class editstring : public string
{
private:
char *str;
unsigned int cursor;
public:
editstring(char *s);
~editstring(){delete str;};
void setcurright()
{
if(cursor<strlen(str)+1)
cursor++;
cout<<"游標點右移為:"<<cursor<<endl;
};
void setcurleft()
{
if(cursor>0)
cursor--;
cout<<"游標點左移為:"<<cursor<<endl;
};
void insert(char c);
void deletes();
char *get(){return str;}
void replace(char c);
void print();
};
editstring::editstring(char *s):string(s)
{
str=new char[strlen(s)+1];
strcpy(str,s);
cursor=0;
cout<<"constructing editstring"<<endl;
}
void editstring::insert(char c)
{
cout<<"插入字母:"<<c<<""<<endl<<"目前游標點為"<<cursor<<""<<endl;
int max=strlen(str)+1;
max++;
char *temp=new char[max];
strcpy(temp,str);
str=new char[max];
strcpy(str,temp);
unsigned int j;
for(j=cursor;j<max-1;j++)
{
str[j+1]=temp[j];
}
str[cursor]=c;
delete []temp; //delete temp.一樣嗎?
}
void editstring::deletes()
{
cout<<"刪除函數被調用。"<<endl<<"目前游標點為"<<cursor<<""<<endl;
int max=strlen(str)+1;
int i=0;
for(i=cursor;i<max;i++)
{
str[i]=str[i+1];
}
i--;
str[i]='\0';
}
void editstring::replace(char c)
{
cout<<"替換函數被調用。"<<endl<<"目前游標點為"<<cursor<<""<<endl;
str[cursor]=c;
}
void editstring::print()
{
cout<<"編輯後的數組為:"<<str<<endl;
}
void main()
{
editstring p("china");
p.string::print();
p.setcurright();
p.setcurright();
p.setcurleft();
p.deletes();
p.editstring::print();
p.insert('a');
p.editstring::print();
p.deletes();
p.editstring::print();
p.replace('s');
p.editstring::print();
}
E. 1)Linux2.6內核下,在編寫字元設備驅動程序時,設備文件節點的創建有幾種方法,分別是什麼
1手動mknod
2 自動調用函數
F. linux中什麼是塊設備和字元設備
塊設備是I/O設備中的一類,是將信息存儲在固定大小的塊中,每個塊都有自己的地址,還可以在設備的任意位置讀取一定長度的數據。數據塊的大小通常在512位元組到32768位元組之間。塊設備的基本特徵是每個塊都能獨立於其它塊而讀寫。
字元設備是在I/O傳輸過程中以字元為單位進行傳輸的設備。在linux系統中,字元設備以特別文件方式在文件目錄樹中占據位置並擁有相應的結點。結點中的文件類型指明該文件是字元設備文件。可以使用與普通文件相同的文件操作命令對字元設備文件進行操作。
(6)linux創建字元設備擴展閱讀:
在大多數的linux操作系統中,塊設備只支持以塊為單位的訪問方式,如磁碟等。KYLIN支持以字元方式來訪問塊設備,即支持以字元為單位來讀寫磁碟等塊設備。所以在/dev目錄中的塊設備,如磁碟等,均以字元設備的外觀出現。
當一台字元型設備在硬體上與主機相連之後,必須為這台設備創建字元特別文件。linux操作系統的mknod命令被用來建立設備特別文件。
參考資料來源:
網路——塊設備
網路——字元設備
G. 嵌入式開發(七):linux字元型設備驅動初步
姓名:王芷若 學號:19020100180
學院:電子工程學院
【嵌牛導讀】:本篇文章整理Linux知識點—Linux字元型設備驅動初步。
【嵌牛鼻子】:Linux設備類型,結構體,驅動模塊
【嵌牛提問】:Linux設備有什麼類型?關鍵函數有哪些?
【嵌牛內容】–linux字元型設備驅動初步
一、Linux字元設備驅動初步
1、Linux設備類型
(1)字元設備:只能一個位元組一個位元組的讀寫的設備,不能隨機讀取設備內存中的某一數據,讀取數據需要按照先後順序進行。字元設備是面向流的設備,常見的字元設備如滑鼠、鍵盤、串口、控制台、LED等。
(2)塊設備:是指可以從設備的任意位置讀取一定長度的數據設備。塊設備如硬碟、磁碟、U盤和SD卡等存儲設備。
(3)網路設備:網路設備比較特殊,不在是對文件進行操作,而是由專門的網路介面來實現。應用程序不能直接訪問網路設備驅動程序。在/dev目錄下也沒有文件來表示網路設備。
2、開發流程
在這里插入圖片描述
3、關鍵函數講解(以2.6以下版本內核為例)
(1)驅動模塊注冊register_chrdev()函數
原型:register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);
major:主設備號,該值為 0 時,自動運行分配。而實際值不是 0 ;
name:設備名稱;
fops:操作函數,實現驅動定義的open、read、write、close等內核函數與應用程序調用的open、read、write、close間的映射;
返回值:
major 值為 0 ,正常注冊後,返回分配的主設備號。如果分配失敗,返回 EBUSY 的負值 ( -EBUSY ) 。major 值若大於 linux/major.h (2.4內核)中聲明的最大值 (#define MAX_CHRDEV 255) ,則返回EINVAL 的負值 (-EINVAL) 。指定 major 值後,若有注冊的設備,返回 EBUSY 的負值 (-EBUSY)。若正常注冊,則返回 0 值
(2)驅動注銷unregister_chrdev()函數
原型:
#include <linux.fs.h>
int unregister_chrdev (unsigned int major, const char *name)
變數:
major 主設備號
name 設備文件
返回值:
major 值若大於 linux/major.h (2.4 內核)中聲明的最大值 (#define MAX_CHRDEV 255),返回 EINVAL的負值 (-EINVAL)。指定了 major的值後,若將要注銷的 major 值並不是注冊的設備驅動程序,返回 EINVAL的負值 ( -EINVAL )。正常注銷則返回 0值。
(3)File_operation結構體
file_operations結構是建立驅動程序和設備編號的連接,內部是一組函數指針,每個打開的文件,也就是file結構,和一組函數關聯,這些操作主要用來實現系統調用的
struct file_operations {
struct mole *owner;//擁有該結構的模塊的指針,一般為THIS_MODULES
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);//僅用於讀取目錄,對於設備文件,該欄位為NULL
unsigned int (*poll) (struct file *, struct poll_table_struct *); //輪詢函數,判斷目前是否可以進行非阻塞的讀寫或寫入
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //執行設備I/O控制命令
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系統,將使用此種函數指針代替ioctl
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系統上,32位的ioctl調用將使用此函數指針代替
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); //通知設備FASYNC標志發生變化
int (*lock) (struct file *, int, struct file_lock *);
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 (*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);
int (*setlease)(struct file *, long, struct file_lock **);
};
H. linux中顯卡是字元設備
是的。
有些字元設備有可以支持隨機訪問,比如顯卡,顯卡也是字元設備。
有時候稱幀緩存設備,我們可以修改某個具體位置的幀緩存數據,從而改變顯示輸出某一些像素點的像素值。
I. linux字元設備驅動簡述-1
Linux系統中,應用程序訪問外設是通過文件的形式來進行的,Linux將所有的外設都看做文件,統一存放在/dev目錄下。
應用程序使用內核提供的標准系統調用來與內核中的驅動程序進行通訊,這些系統調用有:
open(), read(), write(), ioctl(), close() 等等。
file_operations重要的成員
struct inode 結構代表一個實實在在文件,每個文件只對應一個inode;
struct file 結構代表一個打開的文件,同一個文件可以對應多個file結構;
struct file_operations結構代表底層操作硬體函數的集合
找到 first_drv的主設備號是249,如下圖
進階字元設備寫法請看下一篇文章
J. Linux設備驅動開發 新手,創建第一個字元設備驅動時對一些代碼的功能不是很了解,能解釋一下嗎
上層的read函數最終會調用read_test,write亦是如此。
read_test中:
if是判斷是否准備好;for循環是來將要讀取的count個數據寫到buf(用戶空間)中去,這里寫的是『a』.
release_test:
主要作用是將引用計數減1;引用計數是用來統計使用該模塊的次數的。由此推斷在open中有引用計數加一。這樣就將資源的釋放交給了內核,當引用計數為零時,內核將釋放該驅動申請的資源。