余姚玩具厂一变化:十一、Linux驱动程序开发(3) - 字符设备驱动(2)-globalmem设备驱动
来源:百度文库 编辑:偶看新闻 时间:2024/05/05 01:04:18
下面将以linux设备驱动开发详解上的globalmem设备驱动为例来详细分析字符设备驱 动的过程。
#include//模块所需的大量符号和函数定义
#include
#include//文件系统相关的函数和头文件
#include
#include
#include//包含驱动程序使用的大部分内核API的定义,包括睡眠函数以及各种变量声明
#include//指定初始化和清除函数
#include//cdev结构的头文件 包含
#include
#include
#include//在内核和用户空间中移动数据的函数
#define GLOBALMEM_SIZE 0x1000 /*全局内存最大4K字节*/
#define MEM_CLEAR 0x1 /*清0全局内存*/
#define GLOBALMEM_MAJOR 254 /*预设的globalmem的主设备号*/
static int globalmem_major = GLOBALMEM_MAJOR;
/*globalmem设备结构体*/
struct globalmem_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/
};
struct globalmem_dev *globalmem_devp; /*设备结构体指针*/
/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针.
系统在调用驱动程序的open方法前将这个指针置为NULL。
驱动程序可以将这个字段用于任意目的,也可以忽略这个字段。
驱动程序可以用这个字段指向已分配的数据,
但是一定要在内核释放file结构前的release方法中清除它*/
filp->private_data = globalmem_devp;
//将文件私有数据private_data指向设备结构体,read,write,ioctl,llseek等函数
//通过private_data访问设备结构体
return 0;
}
/*文件释放函数*/
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*
备方法应当进行下面的任务:
1、释放 open 分配在 filp->private_data 中的任何东西
2、在最后的 close 关闭设备globalmem_release的基本形式没有硬件去关闭, 因此需要的代码是最少的.不是每个 close 系统调用引起调用 release 方法. 只有真正释放设备数据结构的调用会调用这个方法
*/
/* ioctl设备控制函 数 */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;/*获得设备结构体指针*/
switch (cmd)
{
case MEM_CLEAR://清除全局内存
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zero\n");
break;
default://其他不支持的命令
return - EINVAL;
}
return 0;
}
/*读函数*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)//0x1000 要读的偏移位置越界
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)//要读的字节数太大
count = GLOBALMEM_SIZE - p;
/*内核空间->用户空间*/
//将内核空间的内容复制到用户空间,所复制的内容是从from来,到to去,复制n个字节。
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
}
return ret;
}
/*写函数*/
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
/*
filp 是文件指针,count 是请求的传输数据长度,buff 参数是指向用户空间的缓冲区,这个 缓冲区或者保存要写入的数据,或者是一个存放新读入数据的空缓冲区,该地址在内核空间不能直接读写,offp 是一个指针指向一个"long offset type"对象, 它指出用户正在存取的文件位置. 返回值是一个"signed size type。写的位置相对于文件开头的偏移。
*/
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)//0x1000 要写的偏移位置越界
return count ? - ENXIO: 0; //???
if (count > GLOBALMEM_SIZE - p)//将数据写入该缓存区,直到结尾。这个是判断要写的字节数太多
count = GLOBALMEM_SIZE - p;
/*用户空间->内核空间*/
if (copy_from_user(dev->mem + p, buf, count))
//目的是从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
return ret;//如果ret=count,则完成了所请求数目的字节传送。
}
/* seek文件定位函数 */
/*
seek()函数对文件定位的起始地址可以是文件开头(SEEK_SET,0)、当前位置(SEEK_CUR,1)和文件尾(SEEK_END,2),globalmem支持从文件开头和当前位置相对偏移。在定位的时候,应该检查用 户请求的合法性,若不合法,函数返回- EINVAL,合法时返回文件的当前位置*/
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig)
{
case 0: /*相对文件开始位置偏移*/
if (offset < 0)
{
ret = - EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE)//偏移越界
{
ret = - EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;//struct file
ret = filp->f_pos;
break;
case 1: /*相对文件当前位置偏移*/
if ((filp->f_pos + offset) > GLOBALMEM_SIZE)//偏移越界
{
ret = - EINVAL;
break;
}
if ((filp->f_pos + offset) < 0)
{
ret = - EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = - EINVAL;
break;
}
return ret;//合法时返回文件的当前位置
}
/*文件操作结构体*/
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/*初始化并注册cdev
globalmem_setup_cdev(globalmem_devp, 0);*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops);
/*将 cdev 结构嵌入一个你自己的设备特定的结构,你应当初始化你已经分配的结构使用以上函数,有一个其他的 struct cdev 成员你需要初始化. 象 file_operations 结构,struct cdev 有一个拥 有者成员,应当设置为 THIS_MODULE,一旦 cdev 结构建立, 最后的步骤是把它告诉内核, 调用:
cdev_add(&dev->cdev, devno, 1);*/
/*void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
cdev->kobj.ktype = &ktype_cdev_default;
kobject_init(&cdev->kobj);
cdev->ops = fops;
} */
/*
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
/*globalmem设备结构体*/
struct globalmem_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/
};
*/
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
/*
cdev 是 cdev 结构, devno 是这个设备响应的第一个设备号, count 是应当关 联到设备的设备号的数目. 常常 count 是 1, 但是有多个设备号对应于一个特定的设备的情形. 例如, 设想 SCSI 磁带驱动, 它允许用户空间来选择操作模式(例如密度), 通过安排多个次编号给每一个物理设备.在使用 cdev_add 是 有几个重要事情要记住. 第一个是这个调用可能失败. 如果它返回一个负的错误码,
你的设备没有增加到系统中. 它几乎会一直成功, 但是, 并且带起了其他的点: cdev_add 一 返回, 你的设备就是"活的"并且内核可以调用它的操作. 除非你的驱动完全准备好处理设备上的操作, 你不应当调用 cdev_add */
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*设备驱动模块加载函数*/
/*_init():__init是一个宏,编译时会用到。对于静态编译在内核的 模块有意义.
内核使用了大量不同的宏来标记具有不同作用的函数和数据结构。如 宏__init、__devinit等。
这些宏在include/linux/init.h头文件中定义。编译器通过这些宏可以把代码优化放到合适的内存 位置,
以减少内存占用和提高内核效率。
init():标记内核启动时使用的初始化代码,内核启动完成后不再需要。以此标记的代码位于.init.text内存区域。
#define _ _init _ _attribute_ _ ((_ _section_ _ (".text.init")))
初始化代码的特点是:在系统启动运行,且一旦运行后马上退出内 存,不再占用内存。
*/
int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmem_major, 0);
/*通过主次设备号生成dev_t.
主次设备号的数据类型是dev_t,在/linux/types.h中定义。
在2.6内核中,dev_t是一个32位的数,其中12位用来表示主设备号,
其余20位用来表示次设备号。要获得设备的主次设备号可以使用内核提供的宏:
MAJOR(dev_t dev); #获得主设备号
MINOR(dev_t dev); #获得次设备号
这些宏定义位于linux/kdev_t.h中。如果要把主次设备号转换成dev_t类型,
则可使用:
MKDEV(int major, int minor);*/
/* 申请设备号*/
if (globalmem_major)/*静态申请设备号*/
result = register_chrdev_region(devno, 1, "globalmem");
/*在linux/fs.h中,
devno:是你要分配的起始设备编号. devno的次编号部分常常是 0, 但是没有要求是那个效果;
1:是所请求的连续设备号的个数;
globalmem:是和该设备号范围关联的设备名称,它将出现在/proc/devices或/sysfs中。
如果分配成功则返回0,分配失败则返回一个负的错误码,所请求的设备号无效。
*/
else /* 动态申请设备号*/
{
result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
/*dev_t *dev
dev:是一个只输出的参数, 它在函数成功完成时持有你的分配范围的第一个数;
firstminor:应当是请求的第一个要用的次编号; 它常常是 0;
1: 是所请求的连续设备号的个数;
Globalmem:是和该设备号范围关联的设备名称,它将出现在/proc/devices或/sysfs中。*/
globalmem_major = MAJOR(devno);/*获得主设备号,从dev_t结构中分解出主设备号*/
}
/*
如果globalmem_major=0,利用udev工具自动向系统动态申请未被占用的设备号相关函数定义在include/linux/kdev_t.h中
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
//(1<<20 -1) 此操作后,MINORMASK宏的低20位为1,高12位为0
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
*/
if (result < 0)
return result;
/* 动态申请设备结构体的内存*/
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
/*
在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,或free_pages. 请注意,kmalloc等函数返回的是物理地址!而malloc等返回的是线性地址!要注意kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了。内存映 射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得重新映射以后的地址*/
if (!globalmem_devp) /*申请失败*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
/*memset会将参数globalmem_devp所指的内存区域中前sizeof(struct globalmem_dev)个字节以参数0填入,然后返回指向globalmem_devp的指针*/
globalmem_setup_cdev(globalmem_devp, 0);
/*初始化并注册cdev*/
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
/*释放原先申请的设备号
如果我们不再使用设备号,则要使用unregister_chrdev_region()函数释放它。
devno:是要分配的主设备号范围的起始值,次 设备号一般设置为0;
1:是所请求的连续设备号的个数;*/
return result;
}
/*模块卸载函数*/
/*
__exit,标记退出代码,对于非模块无效。
*/
void globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev); /*注 销cdev,为从系统去除一个字符设备*/
kfree(globalmem_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
/*释放设备号
我们一般在模块的清除函数中调用设备号释放函数。*/
}
MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");//指定代码使用的许可证
module_param(globalmem_major, int, S_IRUGO);
/*module_param宏的第一个参数是选项名,可在/sys虚拟文件系统中该模块的parameter目录中中查看到。第二个参数是选项类型,第三个参数是选项的值*/
module_init(globalmem_init);
module_exit(globalmem_exit);
/*向操作系统注册自己定义的这两个函数,该项目在Kconfig中配置项目为布尔型的话为Y和N两种选项,Y为编译进内核,N不编译,如果为三态型(tristate),为Y,N,M三种选项M为编译为模块,模块也可以在内核编译后根据需要进行手动加载,也可以写个脚本自动加载*/
总结:
字符设备是3大类设备(字符设备、块设备和网络设备)中较简单的一类设备,其驱动程序中完成的主要工作是初始化、添加和删除cdev结构体,申请和释放设备号,以及填充file_opersation结构体中的操作函数,实现file_opersation结构体中的read()、write()和ioctl()等函数是驱动设计的主体工作。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GLOBALMEM_SIZE 0x1000 /*全局内存最大4K字节*/
#define MEM_CLEAR 0x1 /*清0全局内存*/
#define GLOBALMEM_MAJOR 254 /*预设的globalmem的主设备号*/
static int globalmem_major = GLOBALMEM_MAJOR;
/*globalmem设备结构体*/
struct globalmem_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/
};
struct globalmem_dev *globalmem_devp; /*设备结构体指针*/
/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针.
系统在调用驱动程序的open方法前将这个指针置为NULL。
驱动程序可以将这个字段用于任意目的,也可以忽略这个字段。
驱动程序可以用这个字段指向已分配的数据,
但是一定要在内核释放file结构前的release方法中清除它*/
filp->private_data = globalmem_devp;
//将文件私有数据private_data指向设备结构体,read,write,ioctl,llseek等函数
//通过private_data访问设备结构体
return 0;
}
/*文件释放函数*/
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*
备方法应当进行下面的任务:
1、释放 open 分配在 filp->private_data 中的任何东西
2、在最后的 close 关闭设备globalmem_release的基本形式没有硬件去关闭, 因此需要的代码是最少的.不是每个 close 系统调用引起调用 release 方法. 只有真正释放设备数据结构的调用会调用这个方法
*/
/* ioctl设备控制函 数 */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;/*获得设备结构体指针*/
switch (cmd)
{
case MEM_CLEAR://清除全局内存
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zero\n");
break;
default://其他不支持的命令
return - EINVAL;
}
return 0;
}
/*读函数*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)//0x1000 要读的偏移位置越界
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)//要读的字节数太大
count = GLOBALMEM_SIZE - p;
/*内核空间->用户空间*/
//将内核空间的内容复制到用户空间,所复制的内容是从from来,到to去,复制n个字节。
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
}
return ret;
}
/*写函数*/
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
/*
filp 是文件指针,count 是请求的传输数据长度,buff 参数是指向用户空间的缓冲区,这个 缓冲区或者保存要写入的数据,或者是一个存放新读入数据的空缓冲区,该地址在内核空间不能直接读写,offp 是一个指针指向一个"long offset type"对象, 它指出用户正在存取的文件位置. 返回值是一个"signed size type。写的位置相对于文件开头的偏移。
*/
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)//0x1000 要写的偏移位置越界
return count ? - ENXIO: 0; //???
if (count > GLOBALMEM_SIZE - p)//将数据写入该缓存区,直到结尾。这个是判断要写的字节数太多
count = GLOBALMEM_SIZE - p;
/*用户空间->内核空间*/
if (copy_from_user(dev->mem + p, buf, count))
//目的是从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
return ret;//如果ret=count,则完成了所请求数目的字节传送。
}
/* seek文件定位函数 */
/*
seek()函数对文件定位的起始地址可以是文件开头(SEEK_SET,0)、当前位置(SEEK_CUR,1)和文件尾(SEEK_END,2),globalmem支持从文件开头和当前位置相对偏移。在定位的时候,应该检查用 户请求的合法性,若不合法,函数返回- EINVAL,合法时返回文件的当前位置*/
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig)
{
case 0: /*相对文件开始位置偏移*/
if (offset < 0)
{
ret = - EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE)//偏移越界
{
ret = - EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;//struct file
ret = filp->f_pos;
break;
case 1: /*相对文件当前位置偏移*/
if ((filp->f_pos + offset) > GLOBALMEM_SIZE)//偏移越界
{
ret = - EINVAL;
break;
}
if ((filp->f_pos + offset) < 0)
{
ret = - EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = - EINVAL;
break;
}
return ret;//合法时返回文件的当前位置
}
/*文件操作结构体*/
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/*初始化并注册cdev
globalmem_setup_cdev(globalmem_devp, 0);*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops);
/*将 cdev 结构嵌入一个你自己的设备特定的结构,你应当初始化你已经分配的结构使用以上函数,有一个其他的 struct cdev 成员你需要初始化. 象 file_operations 结构,struct cdev 有一个拥 有者成员,应当设置为 THIS_MODULE,一旦 cdev 结构建立, 最后的步骤是把它告诉内核, 调用:
cdev_add(&dev->cdev, devno, 1);*/
/*void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
cdev->kobj.ktype = &ktype_cdev_default;
kobject_init(&cdev->kobj);
cdev->ops = fops;
} */
/*
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
/*globalmem设备结构体*/
struct globalmem_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/
};
*/
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
/*
cdev 是 cdev 结构, devno 是这个设备响应的第一个设备号, count 是应当关 联到设备的设备号的数目. 常常 count 是 1, 但是有多个设备号对应于一个特定的设备的情形. 例如, 设想 SCSI 磁带驱动, 它允许用户空间来选择操作模式(例如密度), 通过安排多个次编号给每一个物理设备.在使用 cdev_add 是 有几个重要事情要记住. 第一个是这个调用可能失败. 如果它返回一个负的错误码,
你的设备没有增加到系统中. 它几乎会一直成功, 但是, 并且带起了其他的点: cdev_add 一 返回, 你的设备就是"活的"并且内核可以调用它的操作. 除非你的驱动完全准备好处理设备上的操作, 你不应当调用 cdev_add */
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*设备驱动模块加载函数*/
/*_init():__init是一个宏,编译时会用到。对于静态编译在内核的 模块有意义.
内核使用了大量不同的宏来标记具有不同作用的函数和数据结构。如 宏__init、__devinit等。
这些宏在include/linux/init.h头文件中定义。编译器通过这些宏可以把代码优化放到合适的内存 位置,
以减少内存占用和提高内核效率。
init():标记内核启动时使用的初始化代码,内核启动完成后不再需要。以此标记的代码位于.init.text内存区域。
#define _ _init _ _attribute_ _ ((_ _section_ _ (".text.init")))
初始化代码的特点是:在系统启动运行,且一旦运行后马上退出内 存,不再占用内存。
*/
int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmem_major, 0);
/*通过主次设备号生成dev_t.
主次设备号的数据类型是dev_t,在/linux/types.h中定义。
在2.6内核中,dev_t是一个32位的数,其中12位用来表示主设备号,
其余20位用来表示次设备号。要获得设备的主次设备号可以使用内核提供的宏:
MAJOR(dev_t dev); #获得主设备号
MINOR(dev_t dev); #获得次设备号
这些宏定义位于linux/kdev_t.h中。如果要把主次设备号转换成dev_t类型,
则可使用:
MKDEV(int major, int minor);*/
/* 申请设备号*/
if (globalmem_major)/*静态申请设备号*/
result = register_chrdev_region(devno, 1, "globalmem");
/*在linux/fs.h中,
devno:是你要分配的起始设备编号. devno的次编号部分常常是 0, 但是没有要求是那个效果;
1:是所请求的连续设备号的个数;
globalmem:是和该设备号范围关联的设备名称,它将出现在/proc/devices或/sysfs中。
如果分配成功则返回0,分配失败则返回一个负的错误码,所请求的设备号无效。
*/
else /* 动态申请设备号*/
{
result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
/*dev_t *dev
dev:是一个只输出的参数, 它在函数成功完成时持有你的分配范围的第一个数;
firstminor:应当是请求的第一个要用的次编号; 它常常是 0;
1: 是所请求的连续设备号的个数;
Globalmem:是和该设备号范围关联的设备名称,它将出现在/proc/devices或/sysfs中。*/
globalmem_major = MAJOR(devno);/*获得主设备号,从dev_t结构中分解出主设备号*/
}
/*
如果globalmem_major=0,利用udev工具自动向系统动态申请未被占用的设备号相关函数定义在include/linux/kdev_t.h中
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
//(1<<20 -1) 此操作后,MINORMASK宏的低20位为1,高12位为0
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
*/
if (result < 0)
return result;
/* 动态申请设备结构体的内存*/
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
/*
在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,或free_pages. 请注意,kmalloc等函数返回的是物理地址!而malloc等返回的是线性地址!要注意kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了。内存映 射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得重新映射以后的地址*/
if (!globalmem_devp) /*申请失败*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
/*memset会将参数globalmem_devp所指的内存区域中前sizeof(struct globalmem_dev)个字节以参数0填入,然后返回指向globalmem_devp的指针*/
globalmem_setup_cdev(globalmem_devp, 0);
/*初始化并注册cdev*/
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
/*释放原先申请的设备号
如果我们不再使用设备号,则要使用unregister_chrdev_region()函数释放它。
devno:是要分配的主设备号范围的起始值,次 设备号一般设置为0;
1:是所请求的连续设备号的个数;*/
return result;
}
/*模块卸载函数*/
/*
__exit,标记退出代码,对于非模块无效。
*/
void globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev); /*注 销cdev,为从系统去除一个字符设备*/
kfree(globalmem_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
/*释放设备号
我们一般在模块的清除函数中调用设备号释放函数。*/
}
MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");//指定代码使用的许可证
module_param(globalmem_major, int, S_IRUGO);
/*module_param宏的第一个参数是选项名,可在/sys虚拟文件系统中该模块的parameter目录中中查看到。第二个参数是选项类型,第三个参数是选项的值*/
module_init(globalmem_init);
module_exit(globalmem_exit);
/*向操作系统注册自己定义的这两个函数,该项目在Kconfig中配置项目为布尔型的话为Y和N两种选项,Y为编译进内核,N不编译,如果为三态型(tristate),为Y,N,M三种选项M为编译为模块,模块也可以在内核编译后根据需要进行手动加载,也可以写个脚本自动加载*/
总结:
字符设备是3大类设备(字符设备、块设备和网络设备)中较简单的一类设备,其驱动程序中完成的主要工作是初始化、添加和删除cdev结构体,申请和释放设备号,以及填充file_opersation结构体中的操作函数,实现file_opersation结构体中的read()、write()和ioctl()等函数是驱动设计的主体工作。
linux 应用程序开发
嵌入式linux开发问题
网卡驱动程序开发
求嵌入式linux开发详细流程(步骤)?
Linux是谁开发出来的?
linux下的图形界面开发
delphi能否开发usb驱动程序
Linux驱动程序的工作原理
Linux下开发和Windows下开发各有啥优缺点?
嵌入式LINUX系统图形界面GUI 开发流程
linux下安装程序图形开发工具
嵌入式Linux开发一定要了解内核吗?
在win中开发linux程序
驱动程序的开发技术有哪些??
用在windows xp的程序能用在linux上吗?(例如驱动程序等)
安装LINUX 出现未找到任何驱动程序
linux安装好了为什么不要驱动程序
无线 UBS网卡 linux 驱动程序源码
闪存在LINUX中要驱动程序吗
初一、十一(打一字)
“十一”
linux?这个是哪国开发的系统。。哪里可以下载
linux是用什么东西开发出来的
linux操作系统的开发公司和国家分别是什么?