2 - 字符设备驱动

博客围绕字符设备实验展开,涉及cdevice.c、app.c、Makefile等文件,并提及了运行效果,但未详细阐述具体内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

//=================================================================================================================================
[1] 一些常用函数接口/结构体
[
	MKDEV();		#获得设备号 #32= major高12bit+次设备号低20bit
	copy_to_user()  #用户层 --> 内核层
	copy_from_user()  #内核层 --> 用户层
	ioremap();
	kmalloc();
	readl();   #读寄存器
	writel();  #写寄存器
	MODULE_LICENSE()
	printk()
	struct class 
	struct device
	struct file_operations 
	register_chrdev()
	class_create()
	device_create()
	device_destroy()
	class_destroy()
	unregister_chrdev()
	kfree()
	iounmap()
	module_init()
	module_exit()
	open()
	write()
	read()
	close()
]

//=================================================================================================================================
[2] 一些设备驱动常识
[
	1>>/proc/devices #注册设备
]

字符设备实验

cdevice.c

字符设备实验
1:用户app.c输入"1"/"0"开关灯功能
=====================================================
======================cdevice.c开始======================
=====================================================
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/device.h>
#include <asm/errno.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h> 

#define GPL2CON    		  0x11000100 //寄存器物理地址
#define GPL2DAT       	  0x11000104
#define CON_RESET        0x00000000 
#define DAT_RESET        0x00


#define GPL2_0_OUT       0x1//输出模式
#define GPL2_0_DATSET    0x1//高电平
MODULE_LICENSE("GPL");

//结构体存储零碎变量信息
struct cdev_led1  {	
	volatile unsigned long *gpl2con; //控制寄存器映射地址
	volatile unsigned long *gpl2dat; //数据寄存器映射地址
	char *name;						  //设备节点文件名字
	unsigned int major;               //主设备号
 	unsigned int minor;               //次设备号
	struct class *cdev_led1_class;  //类结构体
	struct device *cdev_led1_node;  //设备信息结构体
	##############注意结构体里面只能是指针
};

struct cdev_led1 *cled1;  //创建结构体变量

ssize_t cdev_led1_read (struct file *, char __user *, size_t , loff_t *);      //对接应用层的read函数
ssize_t cdev_led1_write (struct file *, const char __user *, size_t , loff_t *);//对接应用层的write函数
int cdev_led1_open (struct inode *, struct file *);                             //对接应用层的open函数
int cdev_led1_close (struct inode *, struct file *);                            //对接应用层的close函数

struct file_operations  fops  = {  //文件操作结构体,注意赋值有"=",成员赋值完后是","最后的成员复制完不写";"
	.read = cdev_led1_read,        
	.write= cdev_led1_write,
	.open= cdev_led1_open,
	.release= cdev_led1_close
};



/*cdev_led1_read() <--> read() //读取驱动文件数据
* struct file //文件路径 
* char __user //接收用户数据的缓存
* size_t      //数据个数
* loff_t      //偏移量
*/
ssize_t cdev_led1_read (struct file *cdev1_file, char __user *cdev1_buf, size_t N, loff_t *offset)
{
	int tem = 0; 
	copy_to_user(cdev1_buf, &tem, 1);  				//内核层---数据---> 用户层
	printk("\n__kernel__ :%s () is %d\n",__FUNCTION__,tem); //打印向用户层发送的数据
	return 0;
}


/*cdev_led1_write() <--> write() //向驱动文件写数据
* struct file //文件路径 
* char __user //接收用户数据的缓存
* size_t      //数据个数
* loff_t      //偏移量
*/
ssize_t cdev_led1_write (struct file *cdev1_file, const char __user *cdev1_buf, size_t N, loff_t *offset)
{

	printk("\n=====__kernel__ :%s in=====\n",__FUNCTION__);


	unsigned int reg_vlu = 0;  					//保存寄存器映射地址的值
	char tem = ' ';
	__copy_from_user(&tem, cdev1_buf,1);     //用户层 ---数据---> 内核
	printk("\n__kernel__ :cdev_led1_write() is %d\n",tem);         //打印从用户层接收的数据

	if(tem == '0')  //关灯
	{
		reg_vlu = readl(cled1->gpl2con);       //读取映射地址数据
		reg_vlu |= (GPL2_0_OUT << 0); 
		writel(reg_vlu,cled1->gpl2con);        //向映射地址写数据
		
		reg_vlu = readl(cled1->gpl2dat);       //读取映射地址数据
		reg_vlu  &= ~(0x1<<0);
		writel(reg_vlu,cled1->gpl2dat);        //向映射地址写数据
		
		printk("\n__kernel__ :%s:close led\n",__FUNCTION__);
	}
	if(tem == '1')  //开灯
	{	
		reg_vlu = readl(cled1->gpl2con);		//读取映射地址数据	
		reg_vlu |= (GPL2_0_OUT << 0); 
		writel(reg_vlu,cled1->gpl2con);		//向映射地址写数据
		
		reg_vlu = readl(cled1->gpl2dat);		//读取映射地址数据
		reg_vlu |= (1<<0);
		writel(reg_vlu,cled1->gpl2dat);        //向映射地址写数据		
	}
	printk("\n=====__kernel__ :%s out=====\n",__FUNCTION__);

	return 0;
}

/*cdev_led1_open() <--> open() 
* struct inode //文件文件描述符 
* struct file //文件路径
*/
int cdev_led1_open (struct inode *cdev1_inode, struct file *cdev1_file)
{
	printk("\n=====__kernel__ :%s in=====\n",__FUNCTION__);
	printk("\n=====__kernel__ :%s out=====\n",__FUNCTION__);
	return 0;
}


/*cdev_led1_close() <--> close() 
* struct inode //文件文件描述符 
* struct file //文件路径
*/
int cdev_led1_close (struct inode *cdev1_inode, struct file *cdev1_file)
{
	printk("\n=====__kernel__ :%s in=====\n",__FUNCTION__);
	printk("\n=====__kernel__ :%s out=====\n",__FUNCTION__);
	return 0;
}


//模块加载函数
static int cde_led1_init(void)
{
	printk("\n=====__kernel__ :%s in=====\n",__FUNCTION__);

	int ret = 0;

	//第一步:实例化结构体变量
	// 分配堆内存 ; 返回值为地址,所以cled1是结构体指针
	cled1 = kmalloc(sizeof(struct cdev_led1), GFP_KERNEL); 
	if(cled1 == NULL)
	{
		printk("\n kmalloc error\n");
		goto err0;  //错误标签0
	}
	//第二步:结构体变量赋值
	cled1->major = 100;
	cled1->minor = 1;
	cled1->name = "cdev1";
	cled1->gpl2con = ioremap(GPL2CON,8);
	cled1->gpl2dat = cled1->gpl2con +1 ;

	//第三步:注册设备,申请设备内存资源:驱动操作函数结构体,主设备号,设备名
	ret = register_chrdev(cled1->major,cled1->name,&fops);
	if(ret == -ENOMEM)
	{
		printk("\n register_chrdev error\n");
		goto err1;
	}
	//第四步:申请类
	cled1->cdev_led1_class = class_create(THIS_MODULE, "cdev_led1_class");    
	if(cled1->cdev_led1_class == NULL)
	{
		printk("\n class_create error\n");
		goto err2;
	}

	
	//第五步:自动创建驱动节点 = 驱动文件
	/*参数2:父亲,一般NULL ; 
	 ;参数4:私有数据填NULL ;参数5:设备节点名字*/
	cled1->cdev_led1_node = device_create(cled1->cdev_led1_class,NULL,MKDEV(cled1->major,cled1->minor), NULL, "cdev1");
	if(cled1->cdev_led1_node == NULL)
	{
			printk("\n device_create error\n");
			goto err3;
	}

	printk("\n=====__kernel__ :%s out=====\n",__FUNCTION__);

	return 0;

//错误处理(释放与回收动作)

err3:
	//第一步:卸载驱动节点
	device_destroy(cled1->cdev_led1_class,MKDEV(cled1->major,cled1->minor));

err2:
	//第二步:卸载类	
	class_destroy(cled1->cdev_led1_class);

err1:
	//第三步:释放驱动占用的内存
	unregister_chrdev(cled1->major,cled1->name);

err0:
	//第四步:释放自定义的结构体对象
	kfree(cled1);
	
	
}


//模块卸载函数
static void cde_led1_exit(void)
{
	printk("\n=====__kernel__ :%s in=====\n",__FUNCTION__);

	//第一步:卸载驱动节点
	device_destroy(cled1->cdev_led1_class,MKDEV(cled1->major,cled1->minor));

	//第二步:卸载类	
	class_destroy(cled1->cdev_led1_class);
	
	//第三步:注销设备,释放驱动占用的内存
	unregister_chrdev(cled1->major,cled1->name);
	
	//第四步:解除寄存器物理地址映射
	iounmap(cled1->gpl2con);

	//第五步:释放自定义的结构体对象
	kfree(cled1);
	
	printk("\n=====__kernel__ :%s out=====\n",__FUNCTION__);
}


module_init(cde_led1_init); //定位模块初始化函数,用户层insmod指令使用该函数跳转到cde_led1_init
module_exit(cde_led1_exit);//定位模块卸载函数,用户层rmmod指令使用该函数跳转到cde_led1_exit


=====================================================
======================cdevice.c结束======================
=====================================================



app.c


=====================================================
======================app.c======================
=====================================================
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

//用户层函数接收main传参
void main(int argc,char*argv[])
{	
printf("\n=====__app__:in=====\n");

	int ret,tem,fd = 0;	
	char ledstate;     

	//第一步:接收用户开关指令,"1" = 开 ; "0" = 关
	ledstate = *(char*)argv[1];

	//第二步:打开驱动节点
	fd = open("/dev/cdev1",O_RDWR);	
	if(fd == -1)
	{
		perror("\nopen error\n");
	}

	//第三步:向驱动写"0" / "1"指令
	ret = write(fd,&ledstate,1);
	if(ret == -1)
    {
        perror("\nwrite error\n");
    }
	
	//第三步:读取驱动数据,并打印
	ret = read(fd,&tem,1);
	printf("\n__app__:read() is %d\n",tem);
    if(ret == -1)
    {
		perror("\nread error\n");
    }

	close(fd);
printf("\n=====__app__:out=====\n");
}

Makefile

obj-m += cdevice.o  #项目模块添加cdevice.o

KERNEL = /home/topeet/iTop4412_Kernel_3.0  #内核源码路径

EXE = app  

GCC = /home/topeet/arm-2009q3/bin/arm-none-linux-gnueabi-gcc #编译工具路径

PWD = $(shell pwd)  #当前路径

all:
	make -C $(KERNEL) M=$(PWD) modules #生成驱动模块
	$(GCC) app.c -o $(EXE)   #编译应用层app.c
install:
	cp cdevice.ko $(EXE) /home/topeet/minilinux/module_pro2/
	#移动.ko 与app.exe到nfs共享目录

运行效果:

[root@iTOP-4412]# insmod cdevice.ko  //加载模块
[13858.429220]
[13858.429232] =====__kernel__ :cde_led1_init in=====
[13858.451033]
[13858.451045] =====__kernel__ :cde_led1_init out=====
[root@iTOP-4412]# ./app 1  //开灯

=====__app__:in[13875.837426]
[13875.837445] =====__kernel__ :cdev_led1_open in=====
[13875.843701]
[13875.843715] =====__kernel__ :cdev_led1_open out=====
[13875.850172]
[13875.850187] =====__kernel__ :cdev_led1_write in=====
[13875.856456]
[13875.856468] __kernel__ :cdev_led1_write() is 49
[13875.862441]
[13875.862455] =====__kernel__ :cdev_led1_write out=====
[13875.869107]
[13875.869121] __kernel__ :cdev_led1_read () is 0
[13875.875346]
[13875.875362] =====__kernel__ :cdev_led1_close in=====
[13875.881371]
[13875.881385] =====__kernel__ :cdev_led1_close out=====
=====

__app__:read() is 0

=====__app__:out=====
[root@iTOP-4412]# ./app 0  //关灯

=====__app__:in[13884.293018]
[13884.293038] =====__kernel__ :cdev_led1_open in=====
=====
[13884.304336]
[13884.304353] =====__kernel__ :cdev_led1_open out=====
[13884.310212]
[13884.310229] =====__kernel__ :cdev_led1_write in=====
[13884.315987]
[13884.316000] __kernel__ :cdev_led1_write() is 48
[13884.321859]
[13884.321872] __kernel__ :cdev_led1_write:close led
[13884.328097]
[13884.328111] =====__kernel__ :cdev_led1_write out=====
[13884.334619]
[13884.334632] __kernel__ :cdev_led1_read () is 0

__app__:read() is[13884.350304]
[13884.350320] =====__kernel__ :cdev_led1_close in=====
 0
[13884.365071]
[13884.365086] =====__kernel__ :cdev_led1_close out=====

=====__app__:out=====
[root@iTOP-4412]# rmmod cdevice  //卸载驱动
[13917.257985]
[13917.258003] =====__kernel__ :cde_led1_exit in=====
[13917.277571]
[13917.277588] =====__kernel__ :cde_led1_exit out=====
[root@iTOP-4412]#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值