Linux字符设备驱动初探

一、设备驱动之数据结构:

 

1、dev_t :设备编号,包含主设备号、次设备号。

dev_t是一个32位的数,12位表示主设备号,20为表示次设备号

(1)主设备号 = MAJORdev_t dev

(2)次设备号 = MINORdev_t dev

(3)设备编号 = MKDEVint major,int minor

 

2file_operations:设备驱动向内核提供的操作函数指针结构

static const  struct  file_operations  XXX_fops =

{

.owner = THIS_MODULE, //拥有该结构的模块指针,防止使用时被卸载

.llseek = XXX_llseek,     //定位函数指针

.open = XXX _open,      //打开设备函数指针

.read = XXX _read,       //读取设备内容,复制内核空间内容到用户空间

.write = XXX _write,      //写入到设备,复制用户空间内容到内核空间

.ioctl =XXX _ioctl,        //I/O操作函数指针

.release = XXX _release,    //释放设备函数指针

};

 

3、file:文件结构体代表一个打开的设备文件

struct file

{  …//字符设备不相关的省略

  const struct file_operations  *f_op;    //文件支持的操作

  unsigned int             f_flags;    //文件标志

  fmode_t                f_mode;   //文件的读写模式

  loff_t                  f_pos;     //当前的读写位置

  struct fown_struct        f_owner;

void    *private_data;//文件私有数据指针,同设备结构体关联

};

 

4inode:信息节点结构,索引节点。管理文件的属性(访问权限、大小、最后修改时间等)

struct inode

{…省略不相关

   umode_t             i_mode; //文件模式,标识字符设备还是块设备

dev_t                i_rdev; //设备编号

struct list_head        i_devices; //块设备链表

union

 {

    struct pipe_inode_info  *i_pipe;

    struct block_device     *i_bdev; //块设备指针

    struct cdev           *i_cdev; //字符设备指针

    };

    void *i_private; //设备私有数据指针

};

 

 

5、cdev:字符设备标准结构体

struct cdev

 {

   struct kobject kobj;            //内嵌内核对象

   struct module *owner;          //所属模块

   const struct file_operations *ops; //文件操作

   struct list_head list;            //包含该类设备的所有inode

   dev_t dev;                  //设备编号

   unsigned int count;           //从设备数量

 };

 

二、设备驱动之内核函数

  

设备驱动实现流程:

   1、分配设备号

//动态分配设备号

int alloc_chrdev_region

(dev_t *dev,           //设备号

unsigned int firstminor,  //第一个次设备号

unsigned int count,      //所请求连续设备编号的个数。

char *name            //设备名称

)

 

//静态分配设备号

int register_chrdev_region

( dev_t first, //预分配的设备号

unsigned int count,//连续设备编号的个数

 char* name//设备名称

)

  2、设备体结构分配空间

void * kmalloc (size_t size, int flags);

size:分配内存大小

flags:要分配内存的类型;值为GFP_KERNEL:表示不能立刻分配内存时,要等待

 

3、        注册设备

//初始化设备

   void cdev_init(struct cdev *cdev, const struct file_operations *fops)

 //添加设备到系统,激活设备

 int cdev_add(struct cdev *p, dev_t dev, unsigned count)

4、释放和注销设备

cdev_del(cdev);   //注销cdev,即从系统中删除此设备

kfree(devp);      //释放结构设备体内存

unregister_chrdev_region(dev_t, 1); //释放设备号

 

5、定义设备文件操作函数

   打开、读写、设备控制等

 

6、声明设备的初始化函数和销毁函数

module_init(xxx_init);  

module_exit(xxx_exit);  

 

 

三、设备驱动实现流程探析

 

1、根据linux规范编写驱动程序:声明、实现驱动的初始化函数和退出函数;实现设备文件操作函数。

2、加载驱动:insmod xxx.so –对应执行模块的初始化函数xxx_init,初始化驱动,同时把设备添加到系统,同时激活设备。这时可以通过lsmodcat /proc/devices查看

3、创建设备节点,相当于在文件目录中添加设备文件

通过Sudo mknod /dev/testdev c 254 0(设备文件名、类型为字符设备、主设备号为254、次设备开始序号为0)命令,创建了/dev/testdev设备节点。

 

4、命令行操作设备:

写数据到设备:echo hello>/dev/testdev 命令终端通过操作系统调用内核驱动的写函数,把用户空间的数据写到内核空间的设备上。

读数据到终端:cat /dev/testdev命令终端通过操作系统调用内核驱动的读函数,把设备上的数据读到命令行终端。

   卸载:rmnod:卸载设备,通过操作系统调用设备驱动的卸载函数

5、用户应用程序操作:

 

a.先打开设备文件,获取文件句柄:

myfile = open("/dev/testdev",O_RDWR):向操作系统,请求打开文件,并传递设备文件的全路径,操作系统调用驱动的open打开函数,并传递该设备相关的inode参数和file参数(在设备加载和创建设备节点时已产生)

 

b.写文件:write(myfile,"hello,",sizeof("hellor"));

调用文件系统的标准写文件函数,通过操作系统,调用内核的设备驱动的写函数,并把参数转换为内核驱动写函数的标准参数。内核驱动的写函数,通过调用内核的函数,从用户空间向内核空间写数据。

C.读文件:read(myfile,buffer,size);

 通过操作系统,调用内核的设备驱动的读函数,通过内核的函数,从内核空间向用户空间的buffer空间写数据。

 

d.设备控制:int ioctl(int fd, ind cmd, …):

 通过操作系统调用内核设备驱动的ioctl函数即可。

 

e. 关闭文件:close(myfile):操作系统做相应的清理工作,但并不卸载设备驱动。

 

 

四、设备驱动程序编写实战

 

你可能感兴趣的:(Linux字符设备驱动初探)