Linux驱动之字符设备的简单分析
下面的字符设备的笔记是基于Linux-2.6.22.6内核
首先问题:应用层是如何调用到驱动层的呢?
VFS架构
用户空间 用户程序(进程)
|
| 文件系统操作的系统调用界面包括 read,write,open,close等等
-------------------------------------------------------------
系统空间 |
|
VFS虚拟文件系统(sys_read(),sys_write() sys_open()等等)
|
|
------------------------------------------------------------- 通过file结构的f_op指针实现的
| | | | “文件系统总线”
| | | |
minix Ext2 FAT 设备文件 ...
说明:
每个进程通过“打开文件”(open)与具体的文件建立起连接,或者说建立起一个读写的”上下文“。
这种连接以一个file数据结构作为代表,结构中有个file_operation结构体指针f_op。指向某个具体的
file_opreation机构,就指定了这个文件所属的文件系统
自己小结:上层的应用程序通过C库调用open()、read()等等,C库有swi value这条指令来触发某个异常,这个异常就会进入内核空间的异常处理函数
会根据swi value传入不同的值来调用sys_read(),sys_write()\sys_open(),然后这些函数再调用到驱动程序的drv_read().drv_write()等等
问:还有怎么open之后跟某个驱动挂接上一起?
答:当你open某个驱动设备时候,比如open某个字符设备,就可以获得它的属性,字符设备的属性是C,然后还有主次设备号
通过该主次设备号,确定到某个具体的字符设备驱动,你字符设备驱动在创建的时候,
比如:devid = MKDEV(major, 0); register_chrdev_region(devid, MAX_PINS, "scx200_gpio");
已经将该字符设备驱动的主次设备号告诉了系统,这样下次系统要调用到该驱动的时候就通过主次设备号将该字符设备驱动打开
然后找到该设备驱动的file_operation,调用它的各种open\read\write函数
记住一句话:注册设备,注册类,在类下面创建设备节点
笔记记录2017/6/25 21:32:
①、假如主设备号写0的话,系统会自动进行分配主设备号
②、应用程序open ("/dev/xxx") 问:/dev/xxx怎么来的???
答:
①:手工创建:mknod /dev/xxx c(字符设备) 111(主设备号) 0(次设备号)---这种方法需要事先知道他的主设备号、次设备号
②:自动创建:udev机制和mdev机制、根据系统的信息自动创建设备节点
#define HELLO_CNT 2
static struct cdev hello_cdev;
static struct class *cls;
dev_t devid;
if (major) {
devid = MKDEV(major, 0);
register_chrdev_region(devid, HELLO_CNT, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
} else {
alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
major = MAJOR(devid);
}
cdev_init(&hello_cdev, &hello_fops);
cdev_add(&hello_cdev, devid, HELLO_CNT);
cls = class_create(THIS_MODULE, "hello");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0");
③、如何建立映射:
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
/*这两个指针指向虚拟地址*/
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
④、内核空间与用户空间如何传递数据???
在点亮LED程序中:
应用层:
if (strcmp(argv[1], "on") == 0)
{
val = 1;
}
else
{
val = 0;
}
write(fd, &val, 4);
驱动层:
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
/*从内核区中读取用户区数据*/
copy_from_user(&val, buf, count);
if (val == 1)
{
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6)); // 点灯
}
else
{
*gpfdat |= (1<<4) | (1<<5) | (1<<6); // 灭灯
}
return 0;
}
函数解析:
copy_from_user(void *to, const void __user *from, unsigned long n)
第一个参数to是内核空间的数据目标地址指针,
第二个参数from是用户空间的数据源地址指针,
第三个参数n是数据的长度。
如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数。
还有:在按键程序中
应用层:read(fd, key_vals, sizeof(key_vals));
驱动层:
ssize_t second_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
unsigned char key_vals[4];
....
key_vals = ...
....
copy_to_user(buf, key_vals, sizeof(key_vals)); /*将数据从内核空间拷贝到用户空间*/
return sizeof(key_vals);
}
函数解析:
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
*to是用户空间的指针,
*from是内核空间指针,
n表示从内核空间向用户空间拷贝数据的字节数
如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
⑤:问:假如我想要分别操作三盏灯的其中一盏灯,或者让它全亮全灭应该怎么做?
答:根据次设备号来进行判断
每个主设备中有255个次设备号,进行申请4个次设备号,每种次设备号对应一种情况。
⑥、问:单片机和linux的硬件操作有什么不一样?
答:单片机直接使用物理地址便可以访问
linux需要使用虚拟地址进行访问,虚拟地址(VA) = ioremap(物理地址(PA),长度)
⑦、