Apakah pemandunya:
Pemandu merangkum operasi peranti perkakasan asas dan menyediakan soket fungsi ke lapisan bawah.
Kategori Peralatan:
Sistem Linux membahagikan peranti kepada tiga kategori: peranti aksara, peranti sekat dan peranti rangkaian.
Peranti aksara: merujuk kepada peranti yang hanya boleh membaca dan menulis bait demi bait Ia tidak boleh membaca data tertentu dalam memori video peranti secara rawak. Peranti aksara ialah peranti berorientasikan aliranPembangunan pemacu Linux Peranti aksara biasa termasuk tetikus, papan kekunci, port bersiri, konsol dan peranti LED. Sekat peranti: merujuk kepada peranti yang boleh membaca ketebalan data tertentu dari mana-mana lokasi pada peranti. Peranti sekat termasuk pemacu keras, cakera, pemacu kilat USB, kad SD, dsb. Peranti rangkaian: Peranti rangkaian boleh menjadi peranti perkakasan, seperti kad rangkaian, tetapi ia juga boleh menjadi peranti perisian semata-mata, seperti soket gelung belakang (lo). Kognisi Terdorong:
Pertama lihat gambar, yang menerangkan proses dan membantu memahami pemandu.
Profil pengguna:
Keadaan kernel:
Susun atur pemacu: Urus pemacu semua peranti, tambah atau cari mereka Penambahan berlaku selepas kami menyusun pemacu dan memuatkannya ke dalam kernel. Carian sedang memanggil pemacu, dan ruang pengguna lapisan aplikasi menggunakan fungsi terbuka untuk mencari. Urutan di mana pemacu dimasukkan ke dalam tatasusunan diambil oleh nombor peranti Maksudnya, nombor peranti utama dan nombor peranti kecil bukan sahaja boleh membezakan jenis peranti dan jenis peranti yang berbeza, tetapi juga boleh memuatkan. pemacu ke kedudukan tertentu dalam tatasusunan, yang diperkenalkan di bawah Pembangunan kod pemacu tidak lebih daripada menambah pemacu (menambah nombor peranti, nama peranti dan fungsi pemacu peranti) dan memanggil pemandu.
Ditambah:
Setiap panggilan sistem sepadan dengan nombor panggilan sistem, dan nombor panggilan sistem sepadan dengan fungsi pemprosesan yang sepadan dalam kernel. Semua panggilan sistem dicetuskan melalui gangguan 0x80. Apabila menggunakan panggilan sistem, nombor panggilan sistem dihantar ke kernel melalui daftar eax Parameter input panggilan sistem dihantar ke kernel secara bergilir-gilir melalui ebx, ecx... Sama seperti fungsi, nilai pulangan bagi. panggilan sistem disimpan dalam eax, dan semua parameter mesti diperoleh daripada eax Cara mengeluarkan pemacu peranti aksara
Prinsip kerja pemacu peranti aksara Dalam dunia Linux, semuanya adalah fail, dan semua operasi peranti perkakasan akan divisualisasikan sebagai operasi fail pada lapisan aplikasi. Kami tahu bahawa jika lapisan aplikasi ingin mengakses peranti perkakasan, ia mesti memanggil pemacu yang sepadan dengan perkakasan. Terdapat begitu banyak pemacu dalam kernel Linux Bagaimana aplikasi boleh memanggil pemacu asas dengan tepat?
Ditambah:
(1) Apabila fungsi terbuka membuka fail peranti, anda boleh mengetahui jenis peranti yang akan dikendalikan (peranti aksara atau peranti blok) berdasarkan maklumat yang diterangkan oleh struktur structinode yang sepadan dengan fail peranti, dan struktur fail struktur akan diperuntukkan.
(2) Mengikut nombor peranti yang direkodkan pada struktur structinode, pemandu yang sepadan boleh ditemui. Di sini kita mengambil peranti watak sebagai contoh. Dalam sistem pengendalian Linux, setiap peranti aksara mempunyai struktur structcdev. Struktur ini menerangkan semua maklumat peranti aksara, yang paling penting ialah soket fungsi operasi peranti aksara.
(3) Selepas mencari struktur structcdev, kernel Linux akan merekodkan alamat pertama ruang memori video di mana struktur structcdev terletak dalam ahli i_cdev struktur structinode, dan merekodkan alamat soket operasi fungsi yang direkodkan dalam struktur structcdev dalam struktur structfile dalam ahli f_ops.
(4) Apabila tugasan selesai, lapisan VFS akan mengembalikan deskriptor fail (fd) kepada aplikasi. Fd ini sepadan dengan struktur structfile. Kemudian aplikasi lapisan bawah boleh mencari structfile melalui fd, dan kemudian mencari fungsi soket file_operation dalam structfile untuk mengendalikan peranti aksara.
Antaranya, cdev_init dan cdev_add telah pun dipanggil dalam fungsi kemasukan pemacu, masing-masing melengkapkan pengikatan peranti aksara dan soket operasi fungsi_fail, dan mendaftarkan pemacu aksara ke kernel.
Cadangan video berkaitan
Alamat pembelajaran percuma: Pembangunan Linux C/C++ (bahagian hadapan/audio dan video/permainan/terbenam/rangkaian berprestasi tinggi/storan/infrastruktur/keselamatan)
Anda memerlukan bahan pembelajaran arkitek pelayan Linux C/C++ dan tambahkan qun579733396 untuk mendapatkannya (bahan termasuk C/C++, Linux, teknologi golang, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, media penstriman, kaedah input CDNlinux, P2P , K8S, Docker, TCP/IP, penterjemah, DPDK, ffmpeg, dsb.), perkongsian percuma
Hubungan antara peranti aksara, pemacu peranti aksara dan program ruang pengguna yang mengakses peranti
Seperti yang ditunjukkan dalam rajah, struktur cdev digunakan dalam kernel Linux untuk menerangkan peranti aksara, dan dev_t ahlinya digunakan untuk menentukan nombor peranti (dibahagikan kepada nombor peranti utama dan kecil) untuk menentukan keunikan peranti aksara. Tentukan fungsi soket yang disediakan oleh pemacu peranti aksara kepada VFS melalui operasi_fail ahlinya, seperti buka biasa(), baca(), tulis(), dsb.
Dalam pemacu peranti aksara Linux, fungsi pemuatan modul memperoleh nombor peranti secara statik atau dinamik melalui register_chrdev_region() atau alloc_chrdev_region(), membina sambungan antara cdev dan operasi_fail melalui cdev_init(), dan menambah cdev pada sistem melalui cdev_add( ) untuk melengkapkan pendaftaran. Fungsi pemunggahan modul melog keluar cdev melalui cdev_del() dan mengeluarkan nombor peranti melalui unregister_chrdev_region().
Atur cara ruang pengguna yang mengakses peranti menggunakan panggilan sistem Linux, seperti buka(), baca(), tulis(), untuk "memanggil" operasi_fail untuk menentukan fungsi soket yang disediakan oleh pemacu peranti aksara kepada VFS.
Langkah pembangunan pemandu
Inti Linux terdiri daripada pelbagai jenis pemacu Kira-kira 85% daripada kod sumber kernel ialah kod pelbagai jenis pemacu. Terdapat rangkaian lengkap pemacu dalam kernel, dan ia boleh ditukar berdasarkan pemacu yang serupa untuk dipadankan dengan papan tertentu.
Kesukaran menulis pemandu bukanlah operasi khusus perkakasan, tetapi memikirkan rangka kerja pemacu sedia ada dan menambah perkakasan pada rangka kerja ini.
Secara umumnya, proses umum menyusun pemacu peranti Linux adalah seperti berikut:
Yang berikut menggunakan kod rangka kerja pemacu peranti aksara ringkas untuk membangunkan dan menyusun pemacu.
Pembangunan kod berdasarkan rangka kerja pemandu
Kod panggilan rendah
#include #include #include #include void main() { int fd,data; fd = open("/dev/pin4",O_RDWR); if(fd<0){ printf("open failn"); perror("reson:"); } else{ printf("open successn"); } fd=write(fd,'1',1); }
Kod rangka kerja pemandu
#include //file_operations声明 #include //module_initmodule_exit声明 #include //__init__exit 宏定义声明 #include //classdevise声明 #include//copy_from_user 的头文件 #include//设备号dev_t 类型声明 #include //ioremap iounmap的头文件 static struct class *pin4_class; static struct device *pin4_class_dev; static dev_t devno;//设备号 static int major = 231; //主设备号 static int minor = 0; //次设备号 static char *module_name = "pin4"; //模块名 //led_open函数 static int pin4_open(struct inode *inode,struct file *file) { printk("pin4_openn");//内核的打印函数和printf类似 return 0; } //led_write函数 static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos) { printk("pin4_writen"); return 0; } static struct file_operations pin4_fops = { .owner = THIS_MODULE, .open= pin4_open, .write = pin4_write, }; int __init pin4_drv_init(void) //真实驱动入口 { int ret; devno = MKDEV(major, minor);//创建设备号 ret = register_chrdev(major, module_name, &pin4_fops);//注册驱动告诉内核,把这个驱动加入到内核驱动的链表中 pin4_class=class_create(THIS_MODULE, "myfirstdemo"); //用代码在dev自动生成设备 pin4_class_dev =device_create(pin4_class, NULL, devno, NULL, module_name);//创建设备文件 return 0; } void __exit pin4_drv_exit(void) { device_destroy(pin4_class, devno); class_destroy(pin4_class); unregister_chrdev(major, module_name);//卸载驱动 } module_init(pin4_drv_init);//入口,内核加载该驱动(insmod)的时候,这个宏被使用 module_exit(pin4_drv_exit); MODULE_LICENSE("GPL v2");
Dikatakan bahawa kesukaran utama dalam pembangunan pemandu adalah untuk memahami kod rangka kerja dan menambah serta menukar peranti padanya. Mari kita lihat logik rangka kerja ini.
Proses reka bentuk rangka kerja terdorong
1. Tentukan nombor peranti utama
2. Tentukan jenis struktur fail_operasi
3 Laksanakan fungsi drv_open/drv_read/drv_write dan lain-lain dan isikan struktur operasi_fail
4. Laksanakan kemasukan pemacu: Semasa memasang pemacu, fungsi entri ini akan dipanggil untuk melaksanakan kerja:
①Beritahu kernel struktur operasi_fail: daftarkan pemacu register_chrdev.
②Buat kelas_buat kelas.
③Buat peranti_cipta.
5 Laksanakan eksport: Apabila pemacu dinyahpasang, fungsi eksport ini akan dipanggil untuk melaksanakan kerja:
①Nyahdaftarkan struktur operasi_fail daripada kernel: unregister_chrdev.
②Hancurkan kelas_cipta.
③Hancurkan nod peranti device_destroy.
6 Pertubuhan lain: Kontrak GPL, pemuatan portal
1 Tentukan peranti utama dan definisi berubah
static struct class *pin4_class; static struct device *pin4_class_dev; static dev_t devno;//设备号 static int major = 231; //主设备号 static int minor = 0; //次设备号 static char *module_name = "pin4"; //模块名
2. Tentukan struktur operasi_fail dan muatkannya ke dalam tatasusunan pemacu kernel
Ini ialah struktur operasi_fail dalam kernel Linux
Tentukan ahli struktur mengikut fungsi panggilan lapisan bawah
static struct file_operations pin4_fops = { .owner = THIS_MODULE, .open= pin4_open, .write = pin4_write, .read= pin4_read, };
3. Laksanakan fungsi seperti pin4_read ahli struktur
static int pin4_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { printk("pin4_readn"); return 0; } //led_open函数 static int pin4_open(struct inode *inode,struct file *file) { printk("pin4_openn");//内核的打印函数和printf类似 return 0; } //led_write函数 static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos) { printk("pin4_writen"); return 0; }
4. Pintu masuk pemandu
int __init pin4_drv_init(void) //真实驱动入口 { int ret; devno = MKDEV(major, minor);//创建设备号 ret = register_chrdev(major, module_name, &pin4_fops);//注册驱动告诉内核,把这个驱动加入到内核驱动的链表中 pin4_class=class_create(THIS_MODULE, "myfirstdemo");//由代码在dev自动生成设备 pin4_class_dev =device_create(pin4_class, NULL, devno, NULL, module_name);//创建设备文件 return 0; }
其中pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代码在dev手动生成设备,除此之外还可以自动生成设备,在dev目录下sudomknod+设备名子+设备类型(c表示字符设备驱动)+主设备号+次设备号。
5、出口
void __exit pin4_drv_exit(void) { device_destroy(pin4_class, devno); class_destroy(pin4_class); unregister_chrdev(major, module_name);//卸载驱动 }
6、GPI合同,入口加载,出口加载
module_init(pin4_drv_init);//入口,内核加载该驱动(insmod)的时候,这个宏被使用 module_exit(pin4_drv_exit); MODULE_LICENSE("GPL v2");
驱动模块代码编译和测试
编译阶段
驱动模块代码编译(模块的编译须要配置过的内核源码,编译、连接后生成的内核模块后缀为.ko,编译过程首先会到内核源码目录下,读取顶楼的Makefile文件,之后再返回模块源码所在目录。)
#include //file_operations声明 #include //module_initmodule_exit声明 #include //__init__exit 宏定义声明 #include //classdevise声明 #include//copy_from_user 的头文件 #include//设备号dev_t 类型声明 #include //ioremap iounmap的头文件 static struct class *pin4_class; static struct device *pin4_class_dev; static dev_t devno;//设备号 static int major = 231;//主设备号 static int minor = 0;//次设备号 static char *module_name = "pin4"; //模块名 static int pin4_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { printk("pin4_readn"); return 0; } //led_open函数 static int pin4_open(struct inode *inode,struct file *file) { printk("pin4_openn");//内核的打印函数和printf类似 return 0; } //led_write函数 static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos) { printk("pin4_writen"); return 0; } static struct file_operations pin4_fops = { .owner = THIS_MODULE, .open= pin4_open, .write = pin4_write, .read= pin4_read, }; int __init pin4_drv_init(void) //真实驱动入口 { int ret; devno = MKDEV(major, minor);//创建设备号 ret = register_chrdev(major, module_name, &pin4_fops);//注册驱动告诉内 核,把这个驱动加入到内核驱动的链表中 pin4_class=class_create(THIS_MODULE, "myfirstdemo");//用代码 在dev自动生成设备 return 0; } void __exit pin4_drv_exit(void) { device_destroy(pin4_class, devno); class_destroy(pin4_class); unregister_chrdev(major, module_name);//卸载驱动 } module_init(pin4_drv_init);//入口,内核加载该驱动(insmod)的时候,这个宏被使>用 module_exit(pin4_drv_exit); MODULE_LICENSE("GPL v2");
将该驱动代码拷贝到linux-rpi-4.14.y/drivers/char目录下文件中(也可选择设备目录下其它文件)
更改该文件夹下Makefile(驱动代码放在那个目录,就更改该目录下的Makefile),将前面的代码编译生成模块,文件内容如右图所示:(-y表示编译进内核,-m表示生成驱动模块,CONFIG_表示是按照config生成的),所以只须要将obj-m+=pin4drive.o添加到Makefile中即可。
回到linux-rpi-4.14.y/编译驱动文件
使用指令:ARCH=armCROSS_COMPILE=arm-linux-gnueabihf-KERNEL=kernel7makemodules进行编译生成驱动模块。
编译生成驱动模块会生成以下几个文件:
.o的文件是object文件,.ko是kernelobject,与.o的区别在于其多了一些sections,例如.modinfo。.modinfosection是由kernelsource里的modpost工具生成的,包括MODULE_AUTHOR,MODULE_DESCRIPTION,MODULE_LICENSE,deviceIDtable以及模块依赖关系等等。depmod工具按照.modinfosection生成modules.dep,modules.*map等文件,便于modprobe更便捷的加载模块。
编译过程中,经历了这样的步骤:先步入Linux内核所在的目录,并编译出pin4drive.o文件,运行MODPOST会生成临时的pin4drive.mod.c文件linux 驱动 开发,而后依据此文件编译出pin4drive.mod.o,然后联接pin4drive.o和pin4drive.mod.o文件得到模块目标文件pin4drive.ko,最后离开Linux内核所在的目录。
将生成的.ko文件发送给猕猴桃派:[email protected]:/home/pi
将pin4test.c(下层调用代码)进行交叉编译后发送给猕猴桃派,就可以看见pi目录下存在发送过来的.ko文件和pin4test这两个文件,
加载内核驱动
sudo insmod pin4drive.ko
加载内核驱动(相当于通过insmod调用了module_init这个宏,之后将整个结构体加载到驱动数组中)加载完成后就可以在dev下边见到名子为pin4的设备驱动(这个和驱动代码上面staticchar*module_name="pin4";//模块名这行代码有关),设备号也和代码上面相关。
lsmod查看系统的驱动模块,执行下层代码,赋于权限
查看内核复印的信息,
dmesg |grep pin4
如右图所示:表示驱动调用成功
在装完驱动后可以使用指令:sudormmod+驱动名(不须要写ko)将驱动卸载。
调用流程:
我们下层空间的open去查找dev下的驱动(文件名),文件名背后包含了驱动的主设备号和次设备号,此时用户open触发一个系统调用linux是什么系统,系统调用经过vfs(虚拟文件系统),vfs按照文件名背后的设备号去调用sys_open去判定,找到内核中驱动数组的驱动位置,再去调用驱动上面自己的dev_open函数
为何生成驱动模块须要在虚拟机上生成?猕猴桃派不行吗?
生成驱动模块须要编译环境(linux源码而且编译,须要下载和系统版本相同的Linux内核源代码),也可以在猕猴桃派里面编译,但在猕猴桃派里编译,效率会很低,要特别久。
Atas ialah kandungan terperinci Susunan pemacu peranti perkakasan asas dimasukkan ke dalam senarai terpaut (pemandu). Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!