linux 字符 驱动 linux好用的文本编辑器
大家好,感谢邀请,今天来为大家分享一下linux 字符 驱动的问题,以及和linux好用的文本编辑器的一些困惑,大家要是还不太明白的话,也没有关系,因为接下来将为大家分享,希望可以帮助到大家,解决大家的问题,下面就开始吧!
Linux字符设备驱动编写基本流程
---简介
Linux下的MISC简单字符设备驱动虽然使用简单,但却不灵活。
只能建立主设备号为10的设备文件。字符设备比较容易理解,同时也能够满足大多数简单的硬件设备,字符设备通过文件系 统中的名字来读取。这些名字就是文件系统中的特殊文件或者称为设备文件、文件系统的简单结点,一般位于/dev/目录下 使用ls进行查看会显示以C开头证明这是字符设备文件crw--w---- 1 root tty 4, 0 4月 14 11:05 tty0。第一个数字是主设备 号,第二个数字是次设备号。
---分配和释放设备编号
1)在建立字符设备驱动时首先要获取设备号,为此目的的必要的函数是register_chrdev_region,在linux/fs.h中声明:int register_chrdev_region(dev_t first, unsigned int count, char*name);first是你想要分配的起始设备编号,first的次编号通 常是0,count是你请求的连续设备编号的总数。count如果太大会溢出到下一个主设备号中。name是设备的名字,他会出 现在/proc/devices和sysfs中。操作成功返回0,如果失败会返回一个负的错误码。
2)如果明确知道设备号可用那么上一个方法可行,否则我们可以使用内核动态分配的设备号int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char*name);dev是个只输出的参数,firstminor请求的第一个要用的次 编号,count和name的作用如上1)对于新驱动,最好的方法是进行动态分配
3)释放设备号,void unregister_chrdev_region(dev_t first unsigned int count);
---文件操作file_operations结构体,内部连接了多个设备具体操作函数。该变量内部的函数指针指向驱动程序中的具体操 作,没有对应动作的指针设置为NULL。
1)fops的第一个成员是struct module*owner通常都是设置成THIS_MODULE。
linux/module.h中定义的宏。用来在他的操作还在被使用时阻止模块被卸载。
2)loff_t(*llseek)(struct file*, loff_t, int);该方法用以改变文件中的当前读/写位置
返回新位置。
3)ssize_t(*read)(struct file*, char __user*, size_t, loff_t*);该函数用以从设备文件
中读取数据,读取成功返回读取的字节数。
4)ssize_t(*write)(struct file*, const char __user*,size_t, loff_t*);该函数用以向设备
写入数据,如果成功返回写入的字节数。
5)int(*ioctl)(struct inode*, struct file*, unsigned int, unsigned long);ioctl系统调用提供
发出设备特定命令的方法。
6)int(*open)(struct inode*, struct file*);设备文件进行的第一个操作,打开设备文件。
7)int(*release)(struct inode*, struct file*);释放文件结构函数指针。
一般初始化该结构体如下:
struct file_operations fops={
.owner= THIS_MODULE,.llseek= xxx_llseek,.read= xxx_read,.write= xxx_write,
.ioctl= xxx_ioctl,.open= xxx_open,.release= xxx_release};
PS:以上的文件操作函数指针并不是全部,只是介绍了几个常用的操作。
---文件结构
struct file定义在linux/fs.h中,是设备驱动中第二个最重要的数据结构,此处的file和
用户空间程序中的FILE指针没有关系。前者位于内核空间,后者位于用户控件。
文件结构代表一个打开的文件。(他不特定给设备驱动;系统中每个打开的文件
有一个关联的struct file在内核空间)。它由内核在open时创建,并可以传递给文件件
操作函数,文件关闭之后,内核释放数据结构。
1)mode_t f_mode。确定文件读写模式
2)loff_t f_ops。当前读写位置
3)unsigned int f_flags。文件标志,O_RDONLY、O_NONBLOCK,
4)struct file_operations*f_op。关联文件相关操作
5)void*private_data。open系统调用设置该指针NULL,指向分配的数据。
6)struct dentry*f_dentry。关联到文件的目录入口dentry结构。
---inode结构
inode结构由内核在内部用来表示文件。它和代表打开文件描述符的文件结构是不
同的。inode结构包含大量关于文件的信息。作为通用规则,这个结构只有两个成
员对驱动代码有作用。
dev_t i_rdev。对于代表设备文件的节点,这个成员包含实际的设备编号。
struct cdev*i_cdev。内核内部结构,代表字符设备。
---字符设备注册
在内核调用你的设备操作前,你编写分配并注册一个或几个struct cdev.
struct cdev*my_cdev= cdev_alloc(); my_cdev-ops= my_fops;
或者定义成static均可。
对定义的cdev变量进行初始化,可以使用专门的函数,或者使用如上的方法。
cdev_init( my_cdev, my_fops);其实上边的两行代码就是做了这个函数的工作。
最后告诉内核该cdev。
cdev_add(struct cdev*dev, dev_t num, unsigned int count);
/*上述总结,到此关于设备文件相关的结构数据以及如何注册销毁等操作相关的
函数基本上都已经介绍完毕。主要的还是要设计具体操作的函数来实现具体的
逻辑操作*/
以下代码整理、摘录自《Android深度探索HAL与驱动开发-李宁》LED驱动篇
#include
#include
#include
#include
#include
#include
#include
#deifne DEVICE_NAME"s3c6410_leds"
#define DEVICE_COUNT 1
#define S3C6410_LEDS_MAJOR 0
#define S3C6410_LEDS_MINOR 234
#define PARAM_SIZE 3
static int major= S3C6410_LEDS_MAJOR;
static int minor= S3C6410_LEDS_MINOR;
static dev_t dev_number;
static int leds_state= 1;
static char*params[]={"string1","string2","string3"};
static iint param_size= PARAM_SIZE;
static struct class*leds_class= NULL;
static int s3c6410_leds_ioctl(struct file*file, unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
unsigned tmp;
case 0:
case 1:
if(arg 4)
return-EINVAL;
tmp= ioread32(S3C64XX_GPMDAT);
if(cmd== 1)
tmp=(~(1 arg));
else
tmp|=(1 arg);
iowrite32(tmp, S3C64XX_GPMDAT);
return 0;
default: return-EINVAL;
}
}
static ssize_t s3c6410_leds_write(struct file*file, const char __user*buf, size_t count, loff_t*ppos)
{
unsigned tmp= count;
unsigned long i= 0;
memset(mem, 0, 4);
if(count 4)
tmp= 4;
if(copy_from_user(mem, buf, tmp))
return-EFAULT;
else{
for( i=0; i4; i++)
{
tmp= ioread32(S3C64XX_GPMDAT);
if(mem[i]=='1')
tmp=(~(1 i));
else
tmp|=(1 i);
iowrite32(tmp, S3C64XX_GPMDAT);
}
return count;
}
}
static struct file_operations dev_fops=
{.owner= THIS_MODULE,.unlocked_ioctl= s3c6410_leds_ioctl,.write= s3c6410_leds_write};
static struct cdev leds_cdev;
static int leds_create_device(void)
{
int ret= 0;
int err= 0;
cdev_init(leds_cdev, dev_fops);
leds_cdev.owner= THIS_MODULE;
if(major 0)
{
dev_number= MKDEV(major,minor);
err= register_chrdev_region(dev_number, DEVICE_COUNT, DEVICE_NAME);
if(err 0)
{
printk(KERN_WANRING"register_chrdev_region errorn");
return err
}
}
else{
err= alloc_chrdev_region(leds_cdev.dev, 10, DEVICE_COUNT, DEVICE_NAME);
if(err 0)
{
printk(KERN_WARNING"alloc_chrdev_region errorn");
return err;
}
major= MAJOR(leds_cdev.dev);
major= MINOR(leds_cdev.dev);
dev_number= leds_cdev.dev;
}
ret= cdev_add(leds_cdev,dev_number, DEVICE_COUNT);
leds_class= class_create(THIS_MODULE, DEVICE_NAME);
device_create(leds_class, NULL, dev_number, NULL, DEVICE_NAME);
return ret;
}
static void leds_init_gpm(int leds_default){
int tmp= 0;
tmp= ioread32(S3C64XX_GPMCON);
tmp=(~0xffff);
tmp|= 0x1111;
iowrite32(tmp,S3C64XX_GPMCON);
tmp= ioread32(S3C64XX_GPMPUD);
tmp=(~0XFF);
tmp|= 0xaa;
iowrite32(tmp,S3C64XX_GPMPUD);
tmp= ioread32(S3C64XX_GPMDAT);
tmp=(~0xf);
tmp|= leds_default;
iowrite32(tmp, S3C64XX_GPMDAT);
}
static leds_init( void)
{
int ret;
ret= leds_create_device();
leds_init_gpm(~leds_state);
printk(DEVICE_NAME"tinitializedn");
return ret;
}
static void leds_destroy_device(void)
{
device_destroy(leds_class, dev_number);
if(leds_class)
class_destroy(leds_class);
unregister_chrdev_region(dev_number, DEVICE_NAME);
}
static void leds_exit(void)
{
leds_destroy_device();
printk(DEVICE_NAME"texitn");
}
module_init(leds_init);
module_exit(leds_exit);
module_param(leds_state, int, S_IRUGO|S_IWUSR);
module_param_array(params, charp,?m_size, S_IRUGO|S_IWUSR);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lining");
linux驱动程序结构框架及工作原理分别是什么
一、Linux device driver的概念\x0d\x0a\x0d\x0a系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:\x0d\x0a\x0d\x0a1、对设备初始化和释放;\x0d\x0a\x0d\x0a2、把数据从内核传送到硬件和从硬件读取数据;\x0d\x0a\x0d\x0a3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;\x0d\x0a\x0d\x0a4、检测和处理设备出现的错误。\x0d\x0a\x0d\x0a在Linux操作系统下有三类主要的设备文件类型,一是字符设备,二是块设备,三是网络设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。\x0d\x0a\x0d\x0a已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。\x0d\x0a\x0d\x0a最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。\x0d\x0a\x0d\x0a二、实例剖析\x0d\x0a\x0d\x0a我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。\x0d\x0a\x0d\x0a由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 open,read,write,close?,注意,不是fopen, fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:\x0d\x0a\x0d\x0aSTruct file_operatiONs{\x0d\x0a\x0d\x0aint(*seek)(struct inode*,struct file*, off_t,int);\x0d\x0a\x0d\x0aint(*read)(struct inode*,struct file*, char,int);\x0d\x0a\x0d\x0aint(*write)(struct inode*,struct file*, off_t,int);\x0d\x0a\x0d\x0aint(*readdir)(struct inode*,struct file*, struct dirent*,int);\x0d\x0a\x0d\x0aint(*select)(struct inode*,struct file*, int,select_table*);\x0d\x0a\x0d\x0aint(*ioctl)(struct inode*,struct file*, unsined int,unsigned long);\x0d\x0a\x0d\x0aint(*mmap)(struct inode*,struct file*, struct vm_area_struct*);\x0d\x0a\x0d\x0aint(*open)(struct inode*,struct file*);\x0d\x0a\x0d\x0aint(*release)(struct inode*,struct file*);\x0d\x0a\x0d\x0aint(*fsync)(struct inode*,struct file*);\x0d\x0a\x0d\x0aint(*fasync)(struct inode*,struct file*,int);\x0d\x0a\x0d\x0aint(*check_media_change)(struct inode*,struct file*);\x0d\x0a\x0d\x0aint(*revalidate)(dev_t dev);\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0a这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。\x0d\x0a\x0d\x0a下面就开始写子程序。\x0d\x0a\x0d\x0a#include基本的类型定义\x0d\x0a\x0d\x0a#include文件系统使用相关的头文件\x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0aunsigned int test_major= 0;\x0d\x0a\x0d\x0astatic int read_test(struct inode*inode,struct file*file,char*buf,int count)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aint left;用户空间和内核空间\x0d\x0a\x0d\x0aif(verify_area(VERIFY_WRITE,buf,count)==-EFAULT)\x0d\x0a\x0d\x0areturn-EFAULT;\x0d\x0a\x0d\x0afor(left= count; left> 0; left--)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0a__put_user(1,buf,1);\x0d\x0a\x0d\x0abuf++;\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0areturn count;\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0a这个函数是为read调用准备的。当调用read时,read_test()被调用,它把用户的缓冲区全部写1。buf是read调用的一个参数。它是用户进程空间的一个地址。但是在read_test被调用时,系统进入核心态。所以不能使用buf这个地址,必须用__put_user(),这是kernel提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数。请参考,在向用户空间拷贝数据之前,必须验证buf是否可用。这就用到函数verify_area。为了验证BUF是否可以用。\x0d\x0a\x0d\x0astatic int write_test(struct inode*inode,struct file*file,const char*buf,int count)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0areturn count;\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0astatic int open_test(struct inode*inode,struct file*file)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aMOD_INC_USE_COUNT;模块计数加以,表示当前内核有个设备加载内核当中去\x0d\x0a\x0d\x0areturn 0;\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0astatic void release_test(struct inode*inode,struct file*file)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aMOD_DEC_USE_COUNT;\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0a这几个函数都是空操作。实际调用发生时什么也不做,他们仅仅为下面的结构提供函数指针。\x0d\x0a\x0d\x0astruct file_operations test_fops={?\x0d\x0a\x0d\x0aread_test,\x0d\x0a\x0d\x0awrite_test,\x0d\x0a\x0d\x0aopen_test,\x0d\x0a\x0d\x0arelease_test,\x0d\x0a\x0d\x0a};\x0d\x0a\x0d\x0a设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(modules),如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。\x0d\x0a\x0d\x0aint init_module(void)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aint result;\x0d\x0a\x0d\x0aresult= register_chrdev(0,"test",&test_fops);对设备操作的整个接口\x0d\x0a\x0d\x0aif(result \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0amain()\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aint testdev;\x0d\x0a\x0d\x0aint i;\x0d\x0a\x0d\x0achar buf[10];\x0d\x0a\x0d\x0atestdev= open("/dev/test",O_RDWR);\x0d\x0a\x0d\x0aif( testdev==-1)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aprintf("Cann't open file\n");\x0d\x0a\x0d\x0aexit(0);\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0aread(testdev,buf,10);\x0d\x0a\x0d\x0afor(i= 0; i< 10;i++)\x0d\x0a\x0d\x0aprintf("%d\n",buf[i]);\x0d\x0a\x0d\x0aclose(testdev);\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0a编译运行,看看是不是打印出全1 \x0d\x0a\x0d\x0a以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,DMA,I/O port等问题。这些才是真正的难点。上述给出了一个简单的字符设备驱动编写的框架和原理,更为复杂的编写需要去认真研究LINUX内核的运行机制和具体的设备运行的机制等等。希望大家好好掌握LINUX设备驱动程序编写的方法。
linux 内核字符驱动char_dev源码分析
内核模块中的字符驱动代码设计简洁,主要功能集中在fs\char_dev.c文件内。分析代码主要从三个方面入手:管理数据结构、设备号管理、以及动态设备号申请。
从管理数据结构出发,涉及`char_device_struct`、`cdev`和`kobj_map`。`char_device_struct`负责设备号分配,管理`dev_t`的使用情况。`cdev`作为对外提供的数据结构,用于设备管理。`kobj_map`则在字符设备内部管理`cdev`,实现更精细的控制。
设备号管理部分,主要基于`struct char_device_struct`进行,结构内存储每个设备号下的次设备。整个管理通过一个大小为255的数组实现,利用哈希表技巧,使得主设备号从0扩展到511都能得到有效管理。通过数组索引与设备号关联,实现设备的动态分配与管理。
静态分配设备号时,`register_chrdev_region()`函数负责按照指定设备号范围进行注册。动态申请设备号则通过`alloc_chrdev_region`函数实现,无需预先了解所有设备号的使用情况。
在动态申请主设备号时,会调用`find_dynamic_major()`函数,实现主设备号的动态分配与管理。这一过程主要通过`__register_chrdev_region`函数,基于同一主设备号申请多个次设备。
`cdev`结构包括申请、初始化、添加、删除等操作,实现与系统更紧密的集成。`cdev_map`则使用`kobj_map`结构,实现`cdev`的内部管理,与字符和块驱动兼容。
通过`chrdev_open`函数,将`file_operations`与文件关联,使应用层能够通过文件操作设备。`cdev_set_parent`函数设置`cdev`的`kobj`父节点,实现更灵活的设备管理。
此外,`device`方式提供了一种将`cdev`与设备结合的手段,实现`sysfs`设备模型与`dev`驱动模型的结合,增强系统兼容性和灵活性。
整个`char_dev`源码设计精妙,通过这些组件和函数,构建了高效、灵活的字符驱动管理机制。