LINUX设备驱动一:编写一个字符设备

前言: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查看。

你可能感兴趣的:(linux学习笔记)