-linux 문자 장치 드라이버 [리눅스 커널의 깊이 이해]

시리즈 함유량
리눅스 커널의 깊이 이해 리눅스 문자 디바이스 드라이버

환경 :

플랫폼 커널 버전 안드로이드 버전
RK3399 Linux4.4 Android7.1

1, 커널 모듈 개발

1.1 커널 모듈

커널 모듈과 응용 프로그램은 많은 차이가 있습니다. 커널 모듈은 커널의 한 자리를 차지하는 자신을 등록하고 완료 초기화 함수의이 부분에서, 서비스 대기하기 위해 온 모듈은 출구에 마지막보다는 응용 프로그램 프로세스가 정리를 처리하지 언제든지 종료 될 수 있어야합니다. 그러나, 커널 모듈 종료 기능이 있는지주의 깊게 모든 작업이 초기화 복원하기 위해 수행 취소합니다. 그렇지 않으면, 시스템 리셋하기 전에 남아있는이 시스템에 남아있다. 모듈 함수의 가장 뛰어난 기능을 사용하면 항상 재부팅 오랜 기간을 통과하지 않습니다 그래서 그것의 제거 기능이 될 수있다, 커널은 개발주기를 단축 할 수 있습니다.

1.2 하역 모듈

모듈 설치 완료 후, 커널에로드 된 insmod커널의 로딩을 완료하는로드 모듈, 코드 및 데이터 모듈을 달성하기 위해. 드라이버 모듈이로드 될 때 모듈 매개 변수를 지정할 수 있습니다. 자명 한 커널 헤더의 중요성. 커널 모듈에 대한 몇 가지 필요한 헤더 파일이 있으며, 거의 모든 모듈이 사용됩니다.

파일 이름 함유량
linux/module.h 먼저 문서로드 모듈에 필요한 기능과 문자 수가 많은
linux/init.h 헤더는 초기화 및 정리 기능을 지정하는 데 사용됩니다
moudleparam.h 헤더 파일은 문제의 모듈 매개 변수를 포함

우리는 알고 Linux지원입니다 GPL모듈도 물론 필수는이를 지정해야하므로, 이러한 일반 대중 라이센스로. 이를 위해 다음과 같은 라인을 포함한다 :
MODULE_LICENSE(”GPL”);

모듈은 또한 설명의 본질을 정의 할 수 있습니다 :

정의 함유량
MODULE_AUTHOR 모듈의 문
MODULE_DESCRIPION 모듈 기능 간단한 문
MODULE_VERSION 버전 정보

lsmod현재 커널에 모듈 정보를로드 알려져있다의 rmmod도구는 커널 모듈을 제거합니다.

1.3 모듈 초기화 및 종료

상관 등록 기능 모듈 초기화 기능 모듈. 다음 실제 모드 초기화 기능은 종종 정의된다 :

static int __init initfunc(void)
{
//Initialization——Handle
)
module_init(init_func)

초기화 기능을 선언해야한다 static,이 파일에 추가하여 표시되지 않은, 의미, 그것은 일반적으로 문이다. __init그냥 기호는 초기화에 사용하기 전에 나타냅니다. moudle init그러나 필수 필수적이다. 기능의 위치를 표시하는 것입니다 초기화이 매크로 정의는, 오브젝트 코드 모듈에 특별 섹션을 추가합니다. 이 매크로 없이는 초기화 함수를 호출하지 않습니다.
정리 기능 모듈의 필수 부분은, 그 모듈이 제거 될 때 불리는 인터페이스 취소 일반적이다. 목적은 점령 시스템 리소스에 반환하는 것입니다. 이 함수는 다음과 같이 정의된다 :

static void __exit cleanup_function(void)
{
/*Cleanup*/
)
module_exit(cleanup_function)

선언 void정리가 더 리턴 값을 작동하지 않기 때문에. exit태그 유사하다 init에만 모듈의 하역. 마지막으로, moudle exit또한 위해 커널은 정리 기능을 찾을 수. 더 청소 기능이없는 경우, 커널은 모듈이 언로드 할 수 없습니다.

1.4 초기화 오류 처리

커널 초기화 함수에 등록 프로세스는, 당신은 항상 반환 값 성공을 확인해야합니다. 필요한 조치가 실패를 다루는 경우 캐스팅해야합니다. 물론 충분, 등록 과정의 오류는 등록 실패하기 전에 조치를 철회해야합니다 발생합니다. 이러한 상관없이 코어 등록 정보. 오류가 발생한다면,이 모듈은 다시 이전에 모든 것을 취소 등록 롤에 반대 순서로 자신을 등록 할 수 있어야합니다. 그렇지 않으면, 포인터가 존재 때문에이 취소되지 않습니다, 최후의 수단에서 불안정, 불안 상태에있는 커널, 종종있는 유일한 방법은 초기화 시점에 처리 오류가 실수하지 않을 수 있도록 시스템을 재부팅하는 것입니다.

2, 메이저와 마이너 번호

Linux파일 시스템에서 파일의 형태로 장치를 렌더링 문자는 이러한 특수 파일은 많은 투명성 기본 차이, 작업의 파일 이름을 통해 액세스 할 수 있습니다. 이 파일은 장치 파일이라고합니다. 협약의 위치는 /dev수, 디렉토리 ls명령을 볼 수 있습니다. 일반적으로 문자를 사용하는 c문자 장치 파일을 표시 할 수 있습니다.

협약에 해당 드라이버는 장치의 주 식별 번호로 접속된다. 실시 예 4의 가상 콘솔 시리얼 단자를 나타낸다. 커널에 의해 할당 된 두 번째 장치 번호를 관리 할 수 ​​있지만. 그것은 장치의 의미 내에서 파일을 결정 커널 결정합니다. 이 기기 번호의 효과 만 마킹 디바이스 드라이버와 관련된.

내부 2.1, 장치 번호를 나타내는

커널 <linux/types.h>정의 dev_t타입 디바이스를 유지하는 것은 일차 및 이차 기기 번호를 포함하는 번호를 나타내는 데 사용되며, 주 및 보조 부품이 포함된다. 커널의 이전 버전, 데이터 유형은 표현 256하나 개의 마스터 장치 수와 256지금까지 그것을 믿고 있지만 충분한 범위하지만, 가정의 일부의 미래를 위해 다시 확장 커널의 새 버전에서 변경 될 수 있음을, 마이너 번호를 dev_t범위를 나타낼 수있다. dev_t이제 32, 비트 12마스터로서 비트 수, 나머지 20보조 장치 번호로서 비트. 물론, 아마도 그것은 또한 관련 모른다 변화는 최고의 직접 사용하지 않아야하지만 따라야 <linux/kdev_t.h>정의 된 매크로를. 주요 확보와 마이너 번호를 사용해야합니다 :

MAJOR(dev_t dev);
MINOR(dev_t dev);

반면에, 나는 주요 알고 마이너 장치 번호도 변환 할 수 있습니다 dev_t입력하지 :
MKDEV(int major, int minor);
데이터 사양의 변화의 결과에 상관없이 향후 변경이 차단되는 방법.

2.2 장치 번호를 할당 및 할당 해제

드라이버는 먼저 사용에 하나 개 이상의 장치 번호를 받아야합니다 쓰기 문자로 시작했다. 오래된 방법은 전제들이 필요한 가능한 숫자의 범위를 명확하게 인식하고, 수동 등록 모드를 사용하는 것입니다. 에 다음 사용하여 <linux/fs.h>함수 선언을 :
int register_chrdev_region(dev_t first, unsigned int count, char *name);

매개 변수 함유량
first 일반적으로 장치 번호, 디바이스 번호 번부터 할당을 요구0
count 번호는 연속 요청의 수를 count너무 크게하지 다음 마스터 장치 번호로 오버 플로우를 일으키는
name 장치 이름은 성공적으로 등록 후 할 수있는 /proc/devices지식에 사로.

문제는 새로운 전략이 그래서 우리는 종종 어려운, 주요 번호를 사용할 수 알고 있다는 것입니다 Linux동적 할당 메커니즘 장치 번호의 결과로, 커널 개발 커뮤니티에 발표했다. 커널의 동적 할당 가능한 수에 의하여 마스터 기기 :

int allocchrdevregion(devt *dev, unsigned int firstminor, unsigned int count, char *name);

매개 변수 함유량
dev 그 결과 유통의 성공은 첫 번째 번호를 저장합니다
firstminor 요청의 마이너 번호, 자주0
count 연속 번호 요청의 수
name 이름

: 어느 쪽 요구 할당 단말기 수는, 해방 후 다음과 같은 기능이 사용되지 않는 사용
void unregister_chrdev_region(dev_t first,unsigned int count);
모듈 보통이 함수가 호출 cleanup함수.

3, 중요한 데이터 구조

등록 장치 번호는 운전의 시작의 준비에 단지 첫 번째 단계입니다, 드라이브 필요가 고려를 포함한다는 많은 구성 요소가 있습니다. 그러나 어떤 경우, 드라이버의 대부분은에 관여 할 것입니다 3매우 중요한 커널 데이터 구조입니다 file_operations, fileinode구조.

3.1, 파일 작업

장치 번호와 장치 드라이버가 표시된 후, 등록 번호 뒤에 얻을, 다음 단계는 장비의 운영 및 관리에 장치 번호를 연결하는 방법을 고려하는 것입니다. 그런 다음 <linux/fs.h>정의 file_operation구조는 연결에 대한 링크를 설정하는 것입니다.

이 구조 정의는 함수 포인터 세트, 장치 객체의 방법을 포함한다. 이 장치의 파일 시스템을 요구한다. file_opermion하거나 구조의 포인터는 관성이라 fops각 개구 장치 파일 (커널이 보유 file
하고 그룹 협회 조작 파일 콘텐츠의 설명에 의해 표현 구조). 이러한 fops각 필드가 달성되도록 동작 절차를 구동이라한다. 일부 작업에 대한 지원하지 않으면 설정할 수 있습니다 NULL.

관찰 할 때 file_operations다양한 방법이 지원에 정의 할 때입니다 많은 매개 변수에 유의합니다 __user수정 된 사용자 공간 포인터, 그것은 직접적으로 사용할 수없는 포인터임을 나타내는 표시를. 다음 짧게 함수의 연산 장치상에서 구현 될 필요가 보여

회원 함유량
struct module *owner 이 멤버는 보여 그들 모두 모듈의 구조에 대한 포인터 만이있다. 일반적으로 초기화 <linux/module.h>정의 된 매크로를THIS MODULE
ssize_t(*read)(struct file*,char __user*,size_t,loft_t*) 이 부재는 판독 장치의 구현의 동작 방법
ssize_t(*write)(struct file*,const char __user*,size_t,loft_t*) 디바이스의 구현의 부재의 쓰기 동작 방법
int(*open)(struct inode*,struct file*) 이것은 일반적으로 제 1 동작과 같은 디바이스 드라이버에 의해 수행되지만, 문은 반드시 달성 할 필요가 없다
이 방법.
int(*ioctl)(struct inode*,struct file*,unsigned int,unsigned long) 상기 방법은 장치에 특정한 명령을 실행할 수
int(*release)(struct inode*,struct file*); 파일 구조는 작동에 의해 참조 할 때

3.2 파일 구조

두 번째 드라이브에 중요한 데이터 구조에 정의되어 구조.<linux/fs.h>struct file

文件结构是内核在open之时创建用来代表一个打开的任意文件。该结构由内核传递给前述定义的文件操作函数,这样一来,打开的内核中表示的文件就与文件操作可以关联起来。文件不使用后关闭时,内核才释放file结构。同样内核源码中我们将struct file指针惯称之为filp(”file pointer”)。
下面简单举例说明struct file比较重要的成员:

成员 内容
mode_t f_mode 表示文件模式。如用FMODE_READ标示文件可读,而FMODE_WRITE标示可写
unsigned int f_flags 表示文件标志,如常见的O_RDONLYO_NONBLOCK等标记
struct file_operations *f_op 表示和文件关联的操作
void *private_data 可用来保存私有状态信息,在跨系统调用的使用中非常有用。

3.3、inode 结构

inode结构是内核中唯一表示文件结点的结构,而file只是内核中表示打开的文件结构。二者含义不可混淆。既然文件可以被打开多次,那么就会有多个file结构,但是唯一标示的文件结点inode却始终只有一个。

虽然inode结构包含了很多文件信息,但是通常只有两个字段在驱动编程中有用武之地。一个是dev_t i_rdev,这表示设备文件的结点,其中有设备编号信息。另外一个是struct cdev *i_cdev,表示了字符设备的内部表示。

i_rdev类型后来发生了改变,使得大量驱动程序被破坏。为防止这类情况,增加了可移植性的宏编码方式从inode中获取主次设备编号:

unsigned int iminor(struct inode*inode); //获取次设备编号
unsigned int imajor(struct inode*inode); //获取主设备编号

4、字符驱动

4.1、字符设备注册

前述部分,提到字符设备在内核中是由struct cdev来表示的。故而在调用设备操作函数前,注册分配一个或几个这样的结构是必须的。该结构及相关辅助函数定义在<linux/cdev.h>中。
旧版的注册字符设备驱动的方式是:
int register_chrdev(unsigned int major,const char *name,struct file_operations *fops);
系统中移除设备的函数接口是:
int unregister_chrdev(unsigned int major,const char *name)

4.2、读和写

那么核心的问题就是如何解决用户缓存区和内核的数据安全交互。这也是这个函数的核心功能。内核提供了类似memcopy功能的函数,来实现跨越内核和用户的数据传递。
unsigned long copy_to_user(void __user *to,const void *from,unsigned long count);

unsigned long copy_from_user(void *to,const void_user *from,unsigned long count);

2个函数除了实现数据copy传递功能外,还对__user用户指针进行安全检查。若为无效指针,就不会进行copy操作。

实际设备的read方法就是使用copy_to_user从内核设备中读数据到用户空间。
write方法使用copy_from_user实现相反的功能。

4.3、ioctl接口

绝大多数设备驱动有着对硬件控制特殊操作能力。而ioctl方法往往是最容易最直接的选择。该方法实现设备文件用户空间的ioctl系统调用。

ioctl方法驱动实现内核原型:
int(*ioctl)(struct inode *node,struct file *flip,unsigned int cmd,unsigned long arg)

inodeflip指针对应于ioctl系统调用中的file描述符fd。而cmd参数对应于系统调用的命令参数,由用户传递进来。而不论系统调用中可选参数部分是指针还是长整型。内核中的实现均使用unsigned long类型来表示。其实由于多种命令选择,可以想象,实现ioctl可能需要选择判断cmd参数的switch结构。
通常可以在头文件中预定义命令编号的方式去实现。

首先应该考虑的是ioctl命令编号在系统中应当是唯一存在的。因为可能发生访问错误设备却使用正确命令的情况。为避免这种错误,Linux中将这些编码规范划分了位段。旧的使用16位整数,高8位是关联这个设备的”魔幻”数而低8位是一个序号,在设备内唯一。使用这种老传统的驱动程序仍非常之多。现在有了新的划分,选择编号可以先参考include/asm/octl.hDocumentation下的ioctl-number.txt文件。新的位段有四种字段:type(即魔幻数)、number(序号)、direction(传送方向)和size(数据大小)。

可以使用定义在<linux/ioctl.h>中的宏来帮助建立命令编号:

命令 内容
_IO(type, nr) 用于构建无参数命令编号
_IOR(type, nr, datatype) 用于构建从驱动中读数据的命令编号
_IOW(type, nr,datatype) 用于构建向驱动中写数据的命令编号
_IOWR(type, nr, datatype) 用于双向传送

参考头文件中有关这些宏的细节。

关于ioctl的实现,也涉及到参数在用户空间和内核的交互传递。有一组定义在<asrn/uaccess.h>中的函数实现特意为数据大小为1248字节进行拷贝传递。不使用copy_to_user等函数是因为它们传输单数据更加快速方便。

put_user(datum,ptr)
传递依赖于sizeof(ptr)大小的datum到用户空间。

get_user(local,ptr)
这个宏定义用来从用户空间接收单个数据并存储于变量local

发布了253 篇原创文章 · 获赞 93 · 访问量 12万+

추천

출처blog.csdn.net/qq_33487044/article/details/104092307