드라이버는 무엇입니까:
드라이버는 기본 하드웨어 장치의 작동을 캡슐화하고 하위 계층에 기능 소켓을 제공합니다.
장비 카테고리:
리눅스 시스템은 장치를 캐릭터 장치, 블록 장치, 네트워크 장치의 세 가지 범주로 나눕니다.
문자 장치: 바이트 단위로만 읽고 쓸 수 있는 장치를 말합니다. 장치의 비디오 메모리에 있는 특정 데이터를 무작위로 읽을 수 없으며 데이터를 순서대로 읽어야 합니다. 문자 장치는 스트림 지향 장치Linux 드라이버 개발입니다. 일반적인 문자 장치에는 마우스, 키보드, 직렬 포트, 콘솔 및 LED 장치가 포함됩니다. 블록 디바이스: 디바이스의 어느 위치에서나 일정 두께의 데이터를 읽을 수 있는 디바이스를 말합니다. 블록 장치에는 하드 드라이브, 디스크, USB 플래시 드라이브, SD 카드 등이 포함됩니다. 네트워크 장치: 네트워크 장치는 네트워크 카드와 같은 하드웨어 장치일 수도 있지만 루프백 소켓(lo)과 같은 순수한 소프트웨어 장치일 수도 있습니다. 주도적 인지:
먼저 과정을 설명하고 운전자를 이해하는 데 도움이 되는 사진을 보세요.
사용자 프로필:
커널 상태:
드라이버 배열: 모든 장치의 드라이버를 관리하고 추가하거나 찾습니다. 추가는 드라이버를 컴파일하고 커널에 로드한 후에 발생합니다. 검색은 드라이버를 호출하고 애플리케이션 계층 사용자 공간은 검색을 위해 open 함수를 사용합니다. 드라이버가 배열에 삽입되는 순서는 장치 번호로 검색됩니다. 즉, 주 장치 번호와 부 장치 번호는 다른 유형의 장치와 다른 유형의 장치를 구별할 수 있을 뿐만 아니라 로드할 수도 있습니다. 드라이버 코드의 개발은 드라이버를 추가(장치 번호, 장치 이름 및 장치 드라이버 기능 추가)하고 드라이버를 호출하는 것 이상입니다.
추가됨:
각 시스템 호출은 시스템 호출 번호에 해당하며, 시스템 호출 번호는 커널의 해당 처리 기능에 해당합니다. 모든 시스템 호출은 인터럽트 0x80을 통해 트리거됩니다. 시스템 호출을 사용할 때 시스템 호출 번호는 eax 레지스터를 통해 커널에 전달됩니다. 시스템 호출의 입력 매개변수는 차례로 ebx, ecx...를 통해 커널에 전달됩니다. 시스템 호출은 eax에 저장되며 모든 매개변수는 eax에서 가져와야 합니다. 문자 장치 드라이버를 꺼내는 방법
문자 장치 드라이버 작동 원리 Linux 세계에서는 모든 것이 파일이며 모든 하드웨어 장치 작업은 애플리케이션 계층에서 파일 작업으로 시각화됩니다. 애플리케이션 계층이 하드웨어 장치에 액세스하려면 하드웨어에 해당하는 드라이버를 호출해야 한다는 것을 알고 있습니다. Linux 커널에는 드라이버가 너무 많습니다. 애플리케이션이 기본 드라이버를 어떻게 정확하게 호출할 수 있습니까?
추가됨:
(1) open 함수가 디바이스 파일을 열면 해당 디바이스 파일에 해당하는 structinode 구조체에 기술된 정보를 바탕으로 동작할 디바이스의 종류(캐릭터 디바이스, 블록 디바이스)를 알 수 있으며, structfile 구조체는 할당됩니다.
(2) structinode 구조체에 기록된 장치 번호에 따라 해당 드라이버를 찾을 수 있습니다. 여기서는 문자 장치를 예로 들어 보겠습니다. Linux 운영 체제에서 각 문자 장치에는 structcdev 구조가 있습니다. 이 구조는 문자 장치의 모든 정보를 설명하며, 그 중 가장 중요한 것은 문자 장치의 작업 기능 소켓입니다.
(3) structcdev 구조를 찾은 후 Linux 커널은 structcdev 구조가 있는 비디오 메모리 공간의 첫 번째 주소를 structinode 구조의 i_cdev 멤버에 기록하고, structcdev 구조에 기록된 함수 연산 소켓 주소를 기록합니다. f_ops 멤버의 structfile 구조에 있습니다.
(4) 작업이 완료되면 VFS 계층은 파일 설명자(fd)를 애플리케이션에 반환합니다. 이 fd는 structfile 구조에 해당합니다. 그런 다음 하위 계층 응용 프로그램은 fd를 통해 structfile을 찾은 다음 structfile에서 문자 장치를 작동시키는 함수 소켓 file_op을 찾을 수 있습니다.
그 중 cdev_init와 cdev_add는 이미 드라이버의 진입 함수에서 호출되어 각각 문자 장치와 file_Operation 함수 작업 소켓의 바인딩을 완료하고 문자 드라이버를 커널에 등록했습니다.
관련 영상 추천
무료 학습 주소: Linux C/C++ 개발(프론트엔드/오디오 및 비디오/게임/임베디드/고성능 네트워크/스토리지/인프라/보안)
C/C++ Linux 서버 아키텍트 학습 자료가 필요하고 qun579733396을 추가하여 획득하세요(자료에는 C/C++, Linux, golang 기술, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, 스트리밍 미디어, CDNlinux 입력 방법 포함) , P2P , K8S, Docker, TCP/IP, 인터프리터, DPDK, ffmpeg 등), 무료 공유
캐릭터 장치, 캐릭터 장치 드라이버 및 장치에 액세스하는 사용자 공간 프로그램 간의 관계
그림에서 볼 수 있듯이 cdev 구조는 Linux 커널에서 문자 장치를 설명하는 데 사용되며, 해당 멤버 dev_t는 문자 장치의 고유성을 결정하기 위해 장치 번호(주 장치 번호와 부 장치 번호로 구분)를 정의하는 데 사용됩니다. 공통 open(), read(), write() 등과 같은 멤버 file_options를 통해 문자 장치 드라이버가 VFS에 제공하는 소켓 함수를 정의합니다.
Linux 문자 장치 드라이버에서 모듈 로딩 함수는 Register_chrdev_region() 또는 alloc_chrdev_region()을 통해 장치 번호를 정적 또는 동적으로 획득하고, cdev_init()를 통해 cdev와 file_Operations 간의 연결을 구축하고, cdev_add()를 통해 시스템에 cdev를 추가합니다. ) 등록을 완료합니다. 모듈 언로드 기능은 cdev_del()을 통해 cdev를 로그아웃하고 unregister_chrdev_region()을 통해 장치 번호를 해제합니다.
장치에 액세스하는 사용자 공간 프로그램은 open(), read(), write()와 같은 Linux 시스템 호출을 사용하여 file_options를 "호출"하여 문자 장치 드라이버가 VFS에 제공하는 소켓 기능을 정의합니다.
드라이버 개발 단계
리눅스 커널은 다양한 유형의 드라이버로 구성되어 있습니다. 커널 소스 코드의 약 85%는 다양한 유형의 드라이버 코드입니다. 커널에는 다양한 드라이버가 있으며 유사한 드라이버를 기반으로 특정 보드와 일치하도록 변경할 수 있습니다.
드라이버 작성 시 어려운 점은 하드웨어의 특정 동작이 아니라 기존 드라이버의 프레임워크를 파악하고 이 프레임워크에 하드웨어를 추가하는 것입니다.
일반적으로 Linux 장치 드라이버를 컴파일하는 일반적인 프로세스는 다음과 같습니다.
다음은 간단한 문자 장치 드라이버 프레임워크 코드를 사용하여 드라이버를 개발하고 컴파일합니다.
드라이버 프레임워크 기반 코드 개발
낮은 호출 코드
으아악
드라이버 프레임워크 코드
으아악
드라이버 개발의 가장 큰 어려움은 프레임워크 코드를 이해하고 그 위에 장치를 추가하고 변경하는 것이라고 합니다. 이 프레임워크의 논리를 살펴보겠습니다.
주도적 프레임워크 설계 프로세스
1. 기본 장치 번호를 결정합니다
2. file_Operations
구조 유형을 정의합니다.3. 해당 drv_open/drv_read/drv_write 및 기타 기능을 구현하고 file_Operations 구조를 채웁니다
4. 드라이버 항목 구현: 드라이버를 설치할 때 작업을 수행하기 위해 이 항목 함수가 호출됩니다.
①커널에 file_Operations 구조를 알려줍니다. 드라이버를 등록하세요.register_chrdev.
②클래스 생성 class_create.
3디바이스 생성 device_create.
5. 내보내기 구현: 드라이버가 제거되면 이 내보내기 기능이 호출되어 작업을 수행합니다.
①커널에서 file_Operations 구조 등록 취소: unregister_chrdev.
②클래스 파괴 class_create.
3디바이스 노드 device_destroy를 파괴합니다.
6. 기타 시설: GPL 계약, 포털 로딩
1. 메인 장치와 변수 정의를 결정합니다
으아악
2. file_Operations 구조를 정의하고 이를 커널 드라이버 배열에 로드합니다
이것은 Linux 커널의 file_Operations 구조입니다
하위 레이어 호출 함수에 따라 구조체 멤버 정의
으아악
3. 구조체 멤버 pin4_read
와 같은 기능을 구현합니다.으아악
4. 운전기사 입장
으아악
其中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内核源代码),也可以在猕猴桃派里面编译,但在猕猴桃派里编译,效率会很低,要特别久。
위 내용은 기본 하드웨어 장치 드라이버가 연결된 목록(드라이버)에 삽입되는 순서의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!