找回密码
 注册
关于网站域名变更的通知
查看: 257|回复: 1
打印 上一主题 下一主题

linux驱动学习知识积累

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-5-12 10:33 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x

) B0 z! V. m* l+ J3 ^( ^一、基础知识扫盲
0 K9 {# I! i* E/ l1、dev_t结构体% x/ w7 ~/ D) u8 r5 w

9 g: {0 V8 |  [2 ^在内核中,dev_t结构体用来保存设备编号信息,在linux/type.h中定义,是一个32位的数,12位表示主设备号+20位的次设备号
4 q0 z5 F0 `3 n: W. y! k5 j- x9 t8 k' O* i8 x$ s
int MAJOR(dev_t dev)//获得dev的主设备号2 t# q) s3 |* ~* y& f% J7 r. }$ H  n
int MINOR(dev_t dev)//获得dev的次设备号: ?" w% N, K5 x7 M9 `
dev_t MKDEV(unsignde int major,unsigned int minor)//由主次设备号获得dev_t数据的宏。
2 J& f) P2 C8 w2 v5 Z, |6 }2、file_opertions结构3 o, h' r( _! @0 Q( W$ {& g

9 O, v+ x- p1 x( k8 Y$ z' J该结构体定义在<linux/fs.h>中,用来建立驱动程序到设备编号的连接,其中每一个成员对应一个系统调用(这里就是所谓的API)。用户进程利用系统调用在对设备文件进行诸如读写操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构对应的函数指针,接着将控制权交给该函数,这就是API所完成的工作。- D: e( I2 ^: t) O/ K9 I+ i0 |; z7 I
' g: b( w  R4 |; m
3、file和inode结构体
& S( v! p" ?) @; _" e3 ]8 K8 z0 l5 v# w
8 n. ]' j$ ^% c' Q, c6 V8 j结构体file在<linux/fs.h>中定义,是一个内核结构,不会出现在用户进程中,与用户空间程序中的file没有任何联系,他表示一个打开的文件,不仅仅限于设备驱动程序,而结构体inode表示一个磁盘上的具体文件。对单个文件,可能会有多个表示打开的文件描述符的file结构, 但是它们都指向单个inode 结构。
+ C0 P- p# I1 b3 a2 w7 O$ g, @, s. P9 Q, z
4、cdev结构体/ k& v2 c& F, u

, I0 p1 c, G% A4 P在/linux/cdev.h定义,专门用来表示字符设备。5 E! }$ ^8 o8 Y! Q4 h
struct cdev {
, Q  t, ~) G& C$ W3 ]3 X    struct kobject                kobj;
& a# c1 N6 ~& f  z    struct module                 *owner;
+ X( Z: n' _0 I! R+ Z7 n: [    const struct file_operations -*ops;( s5 b% M% {' s% L1 b. |7 S
    struct list_head              list;! v. A* f% n' T  N
    dev_t                         dev;
0 U. `  b0 t  ?$ W- X2 Y, h; D    unsigned int                  count;
% _$ a+ [6 \+ c; ]% r* F+ t' d+ V( D};. r1 X2 q' L& p* L- P( S: N- X  r
: p  D0 U5 W: }- c( O
5、注册和注销设备号的相关函数(register_chrdev_region和unregister_chrdev_region)  l! z6 P: o) M. \3 S
) a7 Q" {# z0 `% |4 Z
静态分配注册设备号,是指在事先知道设备主设备号的情况下,通过参数函数指定的第一个设备号而向系统申请分配一定数目的设备号。
7 r% v3 f2 f  Q7 v+ e1 q5 g/ D! `
8 C# Y( S; }1 o5 X' {int register_chrdev_region(dev_t first,unsigned int count, char *name)) t) G6 n& q, G) d" ^5 e+ s
//first:要分配的设备号的初始值* }# I) r: ]+ N& k& s& |
//count:要配置的设备号数目
5 J* l4 P; V$ D2 t/ |//name:要申请设备号的设备名称(在/proc/devices和sysfs中显示)。
  A* t% F, b4 E' C5 Y0 e6 E: {! v* m5 |8 J1 ~( j
动态分配注册设备号,是指通过参数仅设置一个次设备号和药分配的设备数目而系统动态分配所需的设备号。
5 c8 c6 S1 @; ^! L; j. o; b
9 ?# @" N1 e9 f& X2 I2 N7 [3 Qint alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int
2 {6 M, m3 v( S* h# u: vcount,char *name)  C) W0 ~! z6 }! t( g1 h

! L* }! m' ^; N7 ]$ G返回值:成功返回0,失败返回-1。- F" V# K6 h/ R. `" ^" ?

  q$ m$ R$ i) M( k对于注销设备函数为:
6 H. Y+ V; L$ Y( h$ c2 h
0 C) v3 a; Y. q1 x# p3 j' N. n  Hvoid unregister_chrdev_region(dev_t first,unsigned int count)  i; i* q/ z% Z7 x1 Y% C" y8 Y/ i
3 R3 d, T5 ^; G, b/ x! e1 H6 I8 o. s

+ r9 S  B. G# n) b( b1 E6、加载和卸载模块
% B! V; |9 s% x
$ L5 ?, i- g+ binsmod命令和rmmod命令用于加载和卸载模块,使用insmod时,入口点函数是init_module()函数,这里实现设备的注册;使用rmmod命令时的入口函数是cleanup_module函数,在这里实现设备的卸载。(这里参照与非网的字符设备驱动编程)。
; x* T8 e1 R# N2 w8 m+ e3 d
' J* Y3 b$ A7 O9 J( ?7、linux内核模式下的内存分配函数
$ s$ W+ o  n9 H# G$ @% M2 y, O5 D! D( {( A" z" `, H
在linux内核模式下,不能使用用户态的malloc()和free()函数申请和释放内存。进行内核编程时,最常用的的内存申请和释放函数为在linux/kernel.h中声明的kmalloc()和Kfree(),其原型为:
% |/ @- |) r/ {6 s- m+ D! q4 y/ [  \# f
void *kmalloc(unsigned int len,int priority);//priority通常设置成GFP_KERNEL,+ J+ v; H; C! x2 B. K1 M
void kfree(void *_ptr);2 J0 z! y/ c) ^6 x

9 e# J6 x5 y$ Y- M9 [) `  G$ q- Z其分配的内存为内核逻辑地址,和相关联的物理地址仅差一个偏移量,和物理地址的映射是线性的。# j. m# h1 F: i9 l
注:Kmalloc()一般用来分配小于128K的内存,而且必须是2的整数次方,并且不会对所获取的内存清零。如果要分配大块的内存,应使用面向页的技术。, j& a  [: m' A* o( m% [/ \" _% _5 r
) o" r6 g; s' `! M
另一种为vmalloc,分配的是内核虚拟地址,虽然是连续的,但是映射到的物理地址是不连续的,而且可能与物理地址不是一一对应的。其原型为:& A0 V# h6 D' ?# k8 n

/ b4 m+ w( ~- D# h) ]void *vmalloc(unsigned long size)" o: C. P) X( N1 P, t. `/ C; V) @, x! s
void vfree(void *addr)
' v( J" P& W, G( @; A
( S- P8 l! G& b6 v- C; G9 b; z& u( _
4 o# x  Q- x: x9 y( C4 a8、用户态和内核太内存交互
' q! `9 t: }9 a- ]7 n# e* E( U: `% e" Z0 Y; v3 F
由于内核态和用户态使用不同的内核定义,所以二者相互不能直接访问对方的内存。有一些函数在include/asm/uaccess.h中声明:
. u; p! L3 y- u1 J2 q  s; b. L, f
8 |9 i" w) m0 Z3 X0 O% [9 Wint access_ok(int type,const void *addr,unsigned long size);</P>
8 q# P- ?+ u5 L: Z. q6 W( S, w//当一个指针指向用户空间时,必须确保指向的用户地址是合法的,而且对应的页面也已经映射,可以采用access_ok检测。type可以为:VERIFY_READ,VERIFY_WRITE,对</P>
; ]/ C- v& V( D8 @//应内存读写。4 \% q$ Z' T6 O; X" ~. G9 ~: G/ X
unsigned long copy_from_user(void *to,const void *from,unsigned long n);
9 G) W' j. m( b! yunsigned long copy_to_user(void *to,void *from,unsigned long len);
9 o5 o8 `+ m: N8 B( x( Z$ s9 ^) B//函数返回不能被复制的字节数,因此,如果完全复制成功,返回值0。! k, Y) Y, Z8 o# y! X! x6 L
int put_user(dataum,ptr);4 l/ A; T: p1 m* w9 |/ O: q9 z
int get_user(local,ptr);! X' k' x$ \0 I
//内核空间和用户空间的单值交互(如char、int、long)。5 v" B9 N+ o7 G$ O* S" e& p

' o% q1 X0 L/ @# l9 {9、lsmod:用于显示当前系统中加载的模块$ t% A1 `% a, a/ n/ e
0 u) d4 F0 W9 H" O1 `
rmmod:用于卸载当前系统加载的模块: W. Y8 v& e# e' X) ]+ g! J

* F) K* Z5 z/ T10、主设备号和次设备号
" [5 s$ _0 e' W8 D4 g& d. e# Q$ g# [" t! l5 Q! y
主设备号表明设备的类型,与一个确定的驱动程序对应,次设备号通常是用于标明不同的属性,例如不同的使用方法,不同的位置,不同的操作等,它标志着某个具体的物理设备。) i; U$ f9 u" d% W% z/ j* d

# i/ R# Y( [/ h0 Z. Y4 b11、什么是释放设备和关闭设备
7 y+ `% k% d: i* v4 }/ @6 I  Q; B6 a. p0 `$ U0 ?
释放设备表示当前进程释放某个设备,其他进程还可以使用该设备;如果当前进程关闭该设备,则其他进程需要重新打开该设备才能使用。3 u0 @" s) e/ h  j: ]; u
& E5 `( h) s. P7 {! w/ P
12、读写设备1 g8 i. @' _2 T( L  f' S  K5 B" [

5 j7 q# q' }7 V# K读写设备的主要任务就是把内核空间的数据复制到用户空间,或者从用户空间复制到内核空间。其函数原型为:
8 ^5 X6 I0 T+ n: h. s! a) N- H4 z  {$ t' v7 X; m
ssize_t (*read)(struct file *filp, char *buff, size_t count, loff_t
4 }( z2 A+ U. A*offp);
9 J3 R/ J( C  S0 ?ssize_t (*write)(struct file *filp, const char *buff, size_t count, loff_t
3 w$ j$ p8 K3 z9 B6 [/ R*offp)3 l$ O. T! \; u' M
//filp:打开的文件指针
. ^$ l3 P, k" j$ \1 J8 x6 q5 q//buff:用户空间的缓冲数据* Z5 G8 h" X+ Q; y# x
//count:传入的数据长度- H- l6 J7 [: L9 \
//offp:偏移量& {6 V+ L4 \  {6 {
//ssize_t:signed size_t类型
$ ?* y/ {# b- k. H0 _0 G, K2 X6 i//size_t:为unsigned int类型+ ]$ _0 `) q- y+ T( [( c0 ^# p
//loff_t:就是long类型8 r3 _: {0 l, Q. J
//注:一般加上_t的都是标准类型的重定义。
& y6 K2 X( s. ~# d; ^. p+ A
, @8 T( D* h$ x: x! r9 ^( [13、io控制操作  |. c9 c+ \  ]( w$ V' s( d7 X4 x# N
% p) M5 h8 d9 k
大部分设备出了读写操作,还需要硬件配置和控制,具体函数为:在linux/fs.h中定义
1 C4 w: i- z* B, _/ Q
8 T* z% D8 O! u$ T" f1 P6 k9 g6 vint (*ioctl)(struct inode *inode, struct file* filp, unsigned int
$ q5 c' F% I& x1 e/ j5 w% Scmd,unsigned long arg)4 M$ @) v7 C5 H/ c2 S
//inode:文件的内核内部结构指针6 k9 u, p. Q5 x1 B
//filp:被打开的文件指针/ @2 K  M; }; j7 K
//cmd:命令类型! ~+ U& y% c2 S, K: L. g, u
//arg:命令参数
/ i  ]+ l' @; |" L% [9 t8 `* O' T
14、打印消息
8 x5 T0 l" j* j/ o3 }
& _+ V/ c$ d3 B% ~" d如同在编写用户空间的应用程序,打印消息有时是很好的调试手段,也是在代码中很常用的组成部分。其函数原型为printk(),在linux/kernel.h中定义& n* j3 D' z; a6 q( C1 P3 P3 E

" A) p5 H2 M, t5 C! |( Zint printk(const char *fmt,…)' f$ x2 G7 T+ T; h6 I4 c% F! k

3 |: N( R7 }( w. ~1 u; ]//fmt 为优先级定义,可以为KERN_EMERG(紧急时间消息)、KERN_ALERT(需要立即采取动作的情况)、KERN_CRIT(临界状态,通常涉及到严重的硬件和软件操作失败)% _1 H$ ^% H+ `8 D$ T

- [) ]( p0 o( _1 x//KERN_ERR(错误报告)、KERN_WARNING(可能出现的问题提出警告)、KERN_NOTICE(有必要进行提醒的正常情况)、KERN_INFO(提示信息)、KERN_DEBUG(调试信息)3 r" e5 K: o7 d
/ @7 L1 [* ^7 c2 u! o4 m/ G6 {
//…和printf一样,成功则返回0,失败返回-1' `; q* M( [* G2 C: N
2 c& g3 f3 H* V8 }: h
' f( }3 [2 o" {  L. V& ~
15、proc文件系统
- {+ t2 @" j: i: R
1 r) ]5 c7 ?: A; x! Z) _linux内核提供了一种proc文件系统,可以在运行时访问内核内部数据结构,或许有关系统和进程的有用信息,在运行时通过改变内核参数来改变设置。与其他文件系统不同,/proc存在于内存中而不是在硬盘上。. z5 Y4 u2 c' m- U

" t( x5 M; s3 \- H% W16、memset: J  X1 Q9 a+ l! q

( ?0 Q; W4 u8 Q$ V4 i在一段内存中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法。函数原型为:
* \9 y( R0 I' {1 v/ @2 H
# B$ @% V* w3 I' cvoid *memset(void *s,int c, size_t n). B" g5 Q9 s; S- U7 l6 n8 Y. L

& v. G8 K: l* j7 v5 y: Z7 s
: i/ ^4 i5 x. ~3 c17、try_module_get()宏0 |# A2 }. F+ L7 h, w
' O2 q% T  Q  Z/ l4 z, _
include/linux/module.h
2 c( w3 t" Z, r' d& ~: D+ g
) k4 k; v. E8 H! A! T( jint try_module_get(struct module *module); . l6 n5 N; m4 M2 f. ]5 L$ B2 l& b; e. R1 O
//用于增加模块使用计数;若返回为0,表示调用失败,希望使用的模块没有被加载或正在被卸载中<BR>void module_put(struct module % Y! e$ t" i  z- M  I, ~# k1 w6 {
*module); //减少模块使用计数。) @! F7 s8 _/ ^- m7 `2 ?2 K
) w2 p4 ~+ @: J( |+ ~- i7 h6 _
1 k& S6 w' L8 N* ?+ x
18、printk中原始指针必须用%p输出0 ]! F1 i, S8 D! ~! R0 I
! I4 G, O9 O7 R
19、linux驱动中函数都设置成static的原因是:7 K9 \; K& t- J; n) @
- ^/ s& r2 z9 N
linux内核十分宠大,代码量超过百万行。对于C语言的函数和全局变量的作用空间都是全局的,在另外一个文件中,使用extern关键字就可以实现对于其他文件中的全局变量6 [, b7 Q( X! E- @: o" p
和函数的访问。因此,一旦源码中函数名称定义相同,就会出现编译出错。因此,需要引入一些封装的特性,限制源码中函数和变量作用的空间。在前面添加static关键字,
( `( C) c. Y$ Z* q1 j' v+ }其作用范围将缩小到仅仅为当前的文件,而不是整个系统。因此在平时写驱动时,如果函数不需要被其他文件中引用,在前面添加static关键字是一个很好的习惯。
: V) P) ^/ X" @9 m3 }% T. ^1 l9 z0 q7 D

该用户从未签到

2#
发表于 2020-5-12 11:21 | 只看该作者
linux驱动学习知识积累
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-11-26 04:41 , Processed in 0.140625 second(s), 23 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表