第一次字符驱动编程的问题总结

创建字符设备节点的方法

  • mknod /dev/xxx   c  252 0


在编写驱动程序时:

主要作用:1) 指定函数在内存的存放位置

     2)在kernel初始化后期,释放所有这些函数代码所占的内存空间

经常在入口函数和出口函数里加上 __init  ,__exit,作用如下:

module_init除了初始化加载之外,还有后期释放内存的作用。linux kernel中有很大一部分代码是设备驱动代码,这些驱动代码都有初始化和反初始化函数,这些代码一般都只执行一次,为了有更有效的利用内存,这些代码所占用的内存可以释放出来

linux就是这样做的,对只需要初始化运行一次的函数都加上__init属性,__init 宏告诉编译器如果这个模块被编译到内核则把这个函数放到(.init.text)段,module_exit的参数卸载时同__init类似,如果驱动被编译进内核,则__exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作,显然__init和__exit对动态加载的模块是无效的,只支持完全编译进内核。

在kernel初始化后期,释放所有这些函数代码所占的内存空间。连接器把带__init属性的函数放在同一个section里,在用完以后,把整个section释放掉。当函数初始化完成后这个区域可以被清除掉以节约系统内存。Kenrel启动时看到的消息“Freeing unused kernel memory: xxxk freed”同它有关。



一般来说,寄存器的地址是物理地址,而在系统中用的都是虚拟地址,而要实现寄存器的物理地址转化为虚拟地址:

都要在入口函数调用ioremap函数, 定义如下:

void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);

在出口函数调用void iounmap(void * addr);

例:

在入口函数:
static int __init hello_init(void)
{
  voliate unsigned long *gpfcon

  gpfcon = (voliate ussigned long *)iroeamp(0xbfd010C0,16);
}

在出口函数:
static void __exit hello_exit(void)
{
   iounmap(gpfcon);
}

但在loongson开发板上:

而在以mips架构的loongson开发板上的寄存器无需调用ioremap,具体原因是在loongson 开发板实现了寄存器的物理地址等于虚拟地址,,无需转化,

但即使虚拟地址与物理地址相同,都需要经过MMU调度实现虚拟地址转化为物理地址。


在实现用户与内核之间数据的传递时,一般要调用以下两个函数:

作用:从内核区中读取数据到用户区
简述:
头文件:#include <linux/uaccess.h>
  • static inline unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
*to是用户空间的指针,
*from是内核空间指针,
n表示从内核空间向用户空间拷贝数据的字节数

作用:从用户区读取数据到内核区
简述:
  • static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n) 
如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
*to是用户空间的指针,
*from是内核空间指针,
n表示从内核空间从用户空间拷贝数据的字节数

 //上述两个函数针对字针的传递,



// 实现用户与内核值的调用,一般用ioctl函数
ioctl 函数接口语法要点
头文件:#include <linux/fs.h>

  • int(*ioctl)(struct inode* inode, struct file* filp, unsigned int cmd, unsigned long arg)

inode:文件的内核内部结构指针
filp:被打开的文件描述符
cmd:命令类型
arg:命令相关参数



当在内核编译模块时,

WARNING: "register_chrdev_region" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
WARNING: "cdev_add" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
WARNING: "cdev_init" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
WARNING: "alloc_chrdev_region" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
WARNING: "printk" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
WARNING: "unregister_chrdev_region" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
WARNING: "cdev_del" [/home/cam/桌面/hello_drive/hello_drive.ko] undefined!
  CC      /home/cam/桌面/hello_drive/hello_drive.mod.o
/home/cam/桌面/hello_drive/hello_drive.mod.c:8: error: variable '__this_module' has initializer but incomplete type
/home/cam/桌面/hello_drive/hello_drive.mod.c:9: error: unknown field 'name' specified in initializer
/home/cam/桌面/hello_drive/hello_drive.mod.c:9: warning: excess elements in struct initializer
/home/cam/桌面/hello_drive/hello_drive.mod.c:9: warning: (near initialization for '__this_module')
/home/cam/桌面/hello_drive/hello_drive.mod.c:10: error: unknown field 'init' specified in initializer
/home/cam/桌面/hello_drive/hello_drive.mod.c:10: warning: excess elements in struct initializer
/home/cam/桌面/hello_drive/hello_drive.mod.c:10: warning: (near initialization for '__this_module')
/home/cam/桌面/hello_drive/hello_drive.mod.c:14: error: unknown field 'arch' specified in initializer
/home/cam/桌面/hello_drive/hello_drive.mod.c:14: error: 'MODULE_ARCH_INIT' undeclared here (not in a function)
/home/cam/桌面/hello_drive/hello_drive.mod.c:14: warning: excess elements in struct initializer
/home/cam/桌面/hello_drive/hello_drive.mod.c:14: warning: (near initialization for '__this_module')
make[2]: *** [/home/cam/桌面/hello_drive/hello_drive.mod.o] 错误 1

当出现以下情况时:


error:variable '__this_module' has initializer but incomplete type

error: unknown field 'init' specified in initializer


一般是你在内核中未加入模块支持选项

解决办法:make menuconfig 选中enable loadable module suppot选项即可 ,然后重新make



字符设备的生成过程:

1)设备号分配与释放

int register_chrdev_region (dev_t first, unsigned int count, char *name)  // 静态已有的设备号

int alloc_chrdev_region (dev_t *dev, unsigned int firstminor, unsigned int count, char *name)  //动态申请设备号,

void unregister_chrdev_region (dev_t first, unsigned int count)   //注销设备号


2)在获得了系统分配的设备号之后,通过注册设备才能实现设备号和驱动程序之间的关联

各个函数如下:

头文件:#include <linux/cdev.h>        

sturct cdev *cdev_alloc(void)            

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

int cdev_add (struct cdev *cdev, dev_t num, unsigned int count)          

void cdev_del(struct cdev *dev)

在 Linux 内核中使用 struct cdev 结构来描述字符设备,我们在驱动程序中必须将已分配到的设备号以及设备操作接口(即为 struct file_operations 结构)赋予struct cdev 结构变量。1)首先使用 cdev_alloc()函数向系统申请分配 struct cdev 结构, ( 附注:也可以直接定义 struct cdev 结构 ,而不用调用cdev_alloc)

2)再用 cdev_init()函数初始化已分配到的结构并与 file_operations 结构关联起来。

3)最后调用 cdev_add()函数将设备号与 struct cdev 结构进行关联并向内核正式报告新设备的注册,这样新设备可以被用起来了。

流程如下:

sturct cdev *cdev_alloc(void)函数->生成struct cdev-> void cdev_init(struct cdev *cdev, struct file_operations *fops)

->int cdev_add (struct cdev *cdev, dev_t num, unsigned int count)


4)如果要从系统中删除一个设备,则要调用 cdev_del()函数。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章