Linux学习笔记9-Linux驱动【1】

发布时间:2024年01月05日

不知道有没有人和我一样,一开始对Linux驱动和Linux应用概念模糊,现在明确了。

  • 单片机的编程属于裸机,是直接在硬件上运行的程序,不需要操作系统支持,包括之前基于的I.MX6ULL编程都是如此
  • Linux驱动负责将应用程序的请求转化为硬件可以理解的指令,并将硬件的响应传递给应用程序。在Linux中,驱动程序通常以内核模块的形式存在,它们被加载到内核中以支持特定的硬件设备。驱动会调用内核接口提供的API完成设备驱动的注册
  • Linux应用编程是指开发运行在Linux操作系统上的应用程序。可以这么理解,Linux应用就是我们熟悉的windows应用程序,而Linux驱动是windows底层的那些设备驱动,例如声卡驱动,网卡驱动等等。驱动和硬件打交道,应用则不需要
  • Linux开发其实就是这两个方向,驱动和应用。Linux应用使用Qt creator比较多,类似于C#的Winform
  • 接下去的学习就是Linux驱动,即和硬件(设备)打交道,同时也会有一些基于命令行的Linux应用开发作为驱动的测试。

最开始要明确的概念是用户态内核态,即userkernel。写了Linux驱动后需要用应用(App)来操作内核,App属于用户态,和内核是隔绝开的。

Linux中一切都是文件,驱动加载成功后会在/dev生成一个相应的文件,例如/dev/DCmotor,就是直流电机的文件。对该文件进行相应的操作即实现对硬件的操作。
使用open, close, write, read几个函数实现打开设备,关闭设备,向设备写数据实现操作和读取设备状态等功能。这些实现硬件操作的函数集合可以参考include/linux/fs.h中的file_operation结构体。字符设备驱动开发中主要工作就是根据据具体驱动需求来实现这些函数。

在驱动编程时,最好的方式是遵守这种“框架”,即把xxx_open, xxx_read, xxx_write, xxx_release这几个函数都写进去,如果要实现具体功能,则编写相应代码,否则只需要return 0即可。
以一个最简单的LED设备为例,首先建一个字符设备结构体:

struct led_dev{
	dev_t devid;  //设备号
	struct cdev cdev;  //cdev
	struct class *class;  //类
	struct device *device;  //设备
	int major;	//主设备号
	int minor;  //次设备号
} leddev;

设备open的时候将private_data指向设备结构体

static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &leddev;
	return 0;
}

不需要读取led的状态,所以read函数直接返回0即可

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

write比较重要,就是通过这个来控制LED设备的暗灭

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;  
	unsigned char databuf[1];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, cnt); //从用户态拷贝字符到内核态的系统函数
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");  //注意这里不是printf
											 //Linux内核中的打印函数是printk,运行在内核态
											 //printk是可以设置显示级别
											 //高于某个级别的可以显示在控制台上。
		return -EFAULT;	//EFAULT是errno-base.h里面定义的错误编号
						//这里不需要管具体是多少,只把它定义为负值即可
	}

	ledstat = databuf[0];  // 获取状态值 

	if(ledstat == LEDON) {	
		led_switch(LEDON);	
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);	
	}
	return 0;
}
/*
函数参数:
filp-设备文件名
buf-要写入设备的数据,__user修饰的指针表明这个指针指向的数据位于用户空间,不是内核空间
cnt-写入数据长度
offt-相对于文件首地址的偏移
函数返回值:
成功则返回0,返回其他值表示写入失败
*/

releaseread一样,也return 0即可。
下面这段必不可少,是把内核态和用户态对文件的操作链接起来

static struct file_operations leddev_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

之后要有驱动入口函数驱动出口函数,即module_init(xxx_init)module_exit(xxx_exit)。具体的实现函数是

static int __init led_init(void)
{
	...  // 其他的一些初始化部分
	/*创建设备号操作*/
	if(leddev.major){ //定义了设备号,即这个major不为0
		leddev.devid = MKDEV(leddev.major, 0);
		register_chrdev_region(...);  //注册设备号内核函数
	}
	else{ //无设备号
		alloc_chrdev_region(...);  //申请设备号
		leddev.major = MAJOR(leddev.devid); 
		leddev.minor = MINOR(leddev.devid); //获取分配得到的主、次设备号
	}
}

static void __exit xxx_exit(void)  
{
	...  //其他出口操作
	cdev_del(&leddev.cdev);/*  删除cdev */
	unregister_chrdev_region(leddev.devid, NEWCHRLED_CNT); /* 注销设备号 */
	device_destroy(leddev.class, leddev.devid);
	class_destroy(leddev.class);
}

module_init(xxx_init);
module_exit(xxx_exit);  //这两句话不能少

Linux驱动有两种运行方式,一是将驱动编译进Linux Kernel里面,Kernel启动后会自动运行该驱动,二是将驱动编译成模块(.ko文件),用方法二比较多。具体流程:

  • modprobe xxx.ko,通过lsmod查看是否注册成功模块,cat /proc/devices查看系统中设备号和设备;
  • 通过ls /dev/xxx -l命令查看申请到的主设备号和次设备号(不是数字的1,是字母的l);
  • 卸载驱动,rmmod xxx.ko,卸载驱动后在设备里面就找不到xxx了。

可以通过编写基于命令行的应用来测试驱动是否工作正常,这里就不赘述了。
接下去要学习Linux设备树

(未完待续)

文章来源:https://blog.csdn.net/raulcy/article/details/135412527
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。