linux驱动开发-第一个驱动-简单字符设备驱动

linux设备驱动主要分为三类:字符设备驱动、块设备驱动、网络设备驱动

其中字符设备驱动适合用来入门,结构简单,更多关于设备驱动的东西这里就不作说明。

上传了完整项目文档说明和代码:操作系统课程设计-简单字符设备和块设备驱动程序

字符设备驱动开发流程

1.编写驱动代码

      1.1 定义一个cdev 来表示你的驱动所对应的设备

struct cdev chrdev;

       1.2 为你设备申请一个设备号

unsigned int TestMajor=0;// 主设备号
unsigned int TestMinor=0;// 次设备号
char *name="demo1";//驱动名称
dev_t dev_no; //设备号
int ret; // 返回结果
dev_no =MKDEV(TestMajor,TestMinor);
if(dev_no>0){
ret=register_chrdev_region(dev_no, 1,name);//静态注册设备号
}
else{//动态申请设备号
alloc_chrdev_region(&dev_no,TestMinor, 1,name);
}	
if(ret<0){
return ret;
}

       1.3 定义文件操作集

int my_open(struct inode *i,struct file *f){
        printk("cdev init\n");
        return 0;
}

int my_release(struct inode *i,struct file *f){
        printk("cdev release\n");
        return 0;
}

static ssize_t my_write(struct file *f,const char __user *u,size_t l,loff_t *o){
        printk("write string\n");
        return l;
}

static ssize_t my_read(struct file *f,char __user *u,size_t l,loff_t *o){
        printk("read string\n");
        return l;
}

struct file_operations fops={
        .owner=THIS_MODULE,
        .open=my_open,
        .release=my_release,
        .write=my_write,
        .read=my_read
};

 

      1.4 初始化 cdev并添加到内核

cdev_init(&chrdev,&fops);
cdev_add(&chrdev,dev_no,1);//先初始化设备号,然后注册cdev

         1.5 设备号和cdev 的注销

unregister_chrdev_region(dev_no, 1);
cdev_dev(&chrdev);

         在贴上完整代码前先注明一点内容:

          一个驱动程序必须的部分如下:

static int my_init(void){ 
        //驱动的初始化
        return 0;
}

static void my_exit(void){
        //驱动的退出
}

module_init(my_init); //调用系统函数来设置驱动初始化函数
module_exit(my_exit); //设置驱动结束的函数

MODULE_AUTHOR("guoz"); //设置作者 ,非必须
MODULE_DESCRIPTION("this ostest demo1"); //设置描述,非必须
MODULE_LICENSE("GPL"); // 必须加上,含义自行查找

           字符设备驱动涉及到了用户态和核态的信息传递,这里放上两个用户传递信息的系统函数:

#include //头文件
//函数原型
unsigned  long  copy_to_user(void *to,  const void  __user  *from,  usigned long  count);
unsigned  long  copy_from_user(void __user *to,  const void *from,  usigned long  count);

         完整驱动程序代码如下:

#include
#include
#include
#include
#include
#include
#include
#include

struct cdev chrdev;
unsigned int major=0;
unsigned int minor=0;
dev_t dev_no;
int ret;

int my_open(struct inode *i,struct file *f){
	printk("cdev init\n");
	return 0;
}

int my_release(struct inode *i,struct file *f){
	printk("cdev release\n");
	return 0;
}

static ssize_t my_write(struct file *f,const char __user *u,size_t l,loff_t *o){
	char buf[100];
	copy_from_user(buf,u,l);
	printk(KERN_EMERG"write string:%s",buf);
	return l;
}

static ssize_t my_read(struct file *f,char __user *u,size_t l,loff_t *o){
	char *buf="hello,user!";
	copy_to_user(u,buf,strlen(buf));
	printk(KERN_EMERG"read string:%s",buf);
	return l;
}

struct file_operations fops={
	.owner=THIS_MODULE,
	.open=my_open,
	.release=my_release,
	.write=my_write,
	.read=my_read
};

static int my_init(void){
	dev_no=MKDEV(major,minor);
	if(dev_no>0){
		ret=register_chrdev_region(dev_no,1,"demo1");
	}else{
		ret=alloc_chrdev_region(&dev_no,0,1,"demo1");
	}
	if(ret<0){
		return ret;
	}
	cdev_init(&chrdev,&fops);
	chrdev.owner=THIS_MODULE;
	cdev_add(&chrdev,dev_no,1);
	return 0;
}

static void my_exit(void){
	unregister_chrdev_region(dev_no,1);
	cdev_del(&chrdev);
}

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("guoz");
MODULE_DESCRIPTION("this ostest demo1");
MODULE_LICENSE("GPL");

 

2.编写Makefile文件

    用于编译驱动程序,文件可复制粘贴使用,注意修改 obj-m 的值

    

ifneq ($(KERNELRELEASE),)
obj-m := demo1.o
else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	rm -fr .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* Module.*
endif

   这里也有坑注意,只有当该行写的命令时才能用 tab 符号开头,而且字符编码需要用 utf-8

3.检查驱动安装是否完成

     上面的步骤完成后,结果就是两个文件 

demo1.c Makefile

      3.1 在当前目录执行 make 命令(默认执行的是make all)

gz@ubuntu:~/Desktop/demo1$ ls
demo1.c  Makefile  test.c
gz@ubuntu:~/Desktop/demo1$ make
make -C /lib/modules/4.15.0-43-generic/build M=/home/gz/Desktop/demo1 modules
make[1]: Entering directory '/usr/src/linux-headers-4.15.0-43-generic'
  CC [M]  /home/gz/Desktop/demo1/demo1.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/gz/Desktop/demo1/demo1.mod.o
  LD [M]  /home/gz/Desktop/demo1/demo1.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-43-generic'
gz@ubuntu:~/Desktop/demo1$ ls
demo1.c   demo1.mod.c  demo1.o   modules.order   test.c
demo1.ko  demo1.mod.o  Makefile  Module.symvers

      3.2 向内核插入驱动程序并获取设备号

root@ubuntu:/home/gz/Desktop/demo1# insmod demo1.ko 
root@ubuntu:/home/gz/Desktop/demo1# cat /proc/devices 
Character devices:
  ...
243 demo1
  ...

   3.3 添加设备文件

        从上一步可以得知 设备号为 243

root@ubuntu:/home/gz/Desktop/demo1# mknod /dev/demo1 c 243 0
root@ubuntu:/home/gz/Desktop/demo1# ll /dev/demo1 
crw-r--r-- 1 root root 243, 0 Jan 28 22:28 /dev/demo1

 

4.编写简单测试程序

测试文件比较简单,这里直接贴完整代码 (test.c)

#include
#include 
#include
int main(void)
{
int fd;
char *buf="test.c";
char buf2[20];
fd=open("/dev/demo1",O_RDWR);
if(fd<0){
	printf("fd<0\n");
}else{
	printf("fd:%d\n",fd);
}
write(fd,buf,7);
read(fd,buf2,18);
printf("res:%s",buf2);
close(fd);
}

5.检验成果

运行测试程序,结果如下:

root@ubuntu:/home/gz/Desktop/demo1# gcc test.c 
root@ubuntu:/home/gz/Desktop/demo1# ./a.out
fd:3
res:hello,user!root

在驱动程序中,使用printk输出的内容我们不能直接看到,这里采用dmesg命令查看

root@ubuntu:/home/gz/Desktop/demo1# dmesg
...
[  909.055395] cdev init
[  909.055458] write string:test.c
[  909.055461] read string:hello,user!
[  909.055469] cdev release

 

参考文章:字符设备驱动模块与测试代码编写

你可能感兴趣的:(linux)