前言:1. 学习参考的是宋宝华linux设备驱动详解。
2.记下的原因有两个,一是加深印象,二是以后忘了可以快速上手。
一般学习都是从helloworld开始,笔者也是,话不多说贴上链接: https://blog.csdn.net/wait_for_taht_day5/article/details/50404572
一、编译环境
需要对应版本的设备树,可用shell命令查看(uname -a),位于/usr/src目录下。如下:
uname -a :4.15.0-47-generic
/usr/src:linux-headers-4.15.0-47
二、驱动实现
驱动的编写主要包含如下6块
模块加载函数、模块卸载函数、模块许可证声明、模块参数(可选)、模块导出符号(可选)、模块作者等信息声明(可选)
1.最基本模块加载,退出。
module_init(globalmem_init);
module_exit(globalmem_exit);
globalmem_init函数的实现
1)通过主设备号和次设备号生成 dev_t。
MKDEV(int major, int minor)
2)分配设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name);
3)初始化设备
cdev_init(&xxx_dev.cdev, &xxx_fops);
4)加载设备
cdev_add(&xxx_dev.cdev, xxx_dev_no, 1)
5)创建一个总线类型,会在/sys/class下生成对应模块目录(可选)
dev_class = class_create(THIS_MODULE, "mode_class_name");
6)在/dev/下自动生成cdevdemo设备节点,而如果不调用此函数,需要手动mknod来创建设备节点后再访问。(可选)
device_create(dev_class, NULL, dev_t, NULL, "modename");
globalmem_exit的实现
卸载函数要完成与模块加载函数相反的功能,可以用_ _exit修饰卸载函数,其作用是告诉内核如果相关的模块被直接编译进内 核(即 built-in ),则globalmem_exit ()函数会被省略,直接不链进最后的镜像。
void unregister_chrdev_region(dev_t from, unsigned count);/*释放设备号*/
void cdev_del(&globalmem_devp->cdev); /*注销cdev*/
2.模块的申明
MODULE_LICENSE("GPL v2");//必须
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
三、编写驱动用到的一些函数
/*cdev_init ()函数用于初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接*/
void cdev_init(struct cdev *, struct file_operations *);
/*cdev_alloc ()函数用于动态申请一个 cdev 内存*/
struct cdev *cdev_alloc(void);
/**/
void cdev_put(struct cdev *p);
/*cdev_add ()函数向系统添加一个 cdev ,完成字符设备的注册*/
int cdev_add(struct cdev *, dev_t, unsigned);
/*cdev_del ()函数分别向系统删除一个 cdev ,完成字符设备的注销*/
void cdev_del(struct cdev *);
由于用户空间不能直接访问内核空间的内存,因此有一下函数:
/*成功返回0*/
unsigned long copy_from_user(void *to, const void _ _user *from, unsigned long count);
unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count);
简单类型,如 char 、 int 、 long 等,则可以使用简单的 put_user ()和 get_user (),如:
get_user(val, (int *) arg);/* 用户 → 内核, arg 是用户空间的地址 */
put_user(val, (int *) arg);/* 内核 → 用户, arg 是用户空间的地址 */
内核空间虽然可以访问用户空间的缓冲区,但是在访问之前,一般需要先检查其合法性,通过 access_ok ( type , addr , size )进行 判断,以确定传入的缓冲区的确属于用户空间。以上4个函数原型均以用access_ok()检查过其合法性。
Linux 内核的许多安全漏洞都是因为遗忘了这一检查造成的,非法侵入者可以伪造一片内核空间的缓冲区地址传入系统调用的 接口,让内核对这个 evil 指针指向的内核空间填充数据。(http://www.cvedetails.com/ Linux CVE ( Common Vulnerabilities and Exposures )列表)
MAJOR(dev_t dev);(获取主设备好)
MINOR(dev_t dev);(获取次设备号)
MKDEV(int major, int minor);(通过主设备号和次设备号生成 dev_t)
源码C部分:
#include
#include
#include
#include
#include
#include
#define GLOBALMEM_SIZE 0x1000
#define MEM_CLEAR 0x1
#define GLOBALMEM_MAJOR 0
//#define GLOBALMEM_MAGIC 'g'
//#define MEM_CLEAR _IO(GLOBALMEM_MAGIC,0)
static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major, int, S_IRUGO);
struct globalmem_dev {
struct cdev cdev;
unsigned char mem[GLOBALMEM_SIZE];
};
struct globalmem_dev *globalmem_devp;
static int globalmem_open(struct inode *inode, struct file *filp)
{
filp->private_data = globalmem_devp;
return 0;
}
static int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
static long globalmem_ioctl(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)
return 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
if (copy_to_user(buf, dev->mem + p, count)) {
ret = -EFAULT;
} else {
*ppos += count;
ret = count;
printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
}
return ret;
}
static ssize_t globalmem_write(struct file *filp, const 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)
return 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
if (copy_from_user(dev->mem + p, buf, count))
ret = -EFAULT;
else {
*ppos += count;
ret = count;
printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
}
return ret;
}
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;
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,
.unlocked_ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);
}
static int __init globalmem_init(void)
{
int ret;
dev_t devno = MKDEV(globalmem_major, 0);
if (globalmem_major)
ret = register_chrdev_region(devno, 1, "globalmem");
else {
ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");
globalmem_major = MAJOR(devno);
}
if (ret < 0)
return ret;
globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
if (!globalmem_devp) {
ret = -ENOMEM;
goto fail_malloc;
}
globalmem_setup_cdev(globalmem_devp, 0);
device_create(class_create(THIS_MODULE, "globalmem"), NULL, MKDEV(globalmem_major, 0), NULL, "globalmem");
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return ret;
}
module_init(globalmem_init);
static void __exit globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev);
kfree(globalmem_devp);
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}
module_exit(globalmem_exit);
MODULE_AUTHOR("Barry Song ");
MODULE_LICENSE("GPL v2");
源码Makefile部分
ifneq ($(KERNELRELEASE),)
obj-m := cdev_globalmem.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers
make会进入KERNEL_DIR目录执行此目录下的Makefile,然后在返回PWD目录执行自己写的Makefile
延伸:
which + 可执行文件(查找可执行文件路径)
dpkg - - get - selections |grep +安装包关键字(查找安装包)
apt-get purge + 安装包(卸载安装包)
du -h --max-depth=0&1(以单位(MB)显示当前文件&文件夹下对应各文件及路径的大小)
dmesg:查看内核日志,包括printk打印日志。
衍生:1.printk打印日志在/var/log/messages目录下,可以使用shell命令 dmesg查看。