操作系统被设计为了对设备使用者隐藏底层硬件细节。Linux需要一种能够将数据从内核态传递给用户态的机制。这种传递通过设备节点来处理,即虚拟文件(/dev/xxx)。当用户获取设备节点时候,内核将底层数据流拷贝到应用程序的内存空间,当用户写设备节点时候,内核将应用程序提供的数据拷贝到数据缓存区,这些数据最终送到底层硬件,这些虚拟文件可以被用户应用程序通过标准的系统调用打开、读取、写入。
Linux应用程序分为三类:字符设备、块设备、网络设备。
字符设备:
是常见的设备,这种设备的读写是直接进行,无需缓冲区,如键盘、鼠标、LED灯、显示器、串口、打印机等;
块设备:
读写以块大小为单位,一次读写整数倍的块大小,如512或者1024字节。
网络设备:
通过BSD套接字接口和网络子系统来访问。
从应用的角度看,一个字符设备本质上是一个文件。通过file_operations数据结构来描述的各种操作并注册。其中,file_operations定义在
include/linux.fs.h
其中一种从用户态和内核态实现数据交互的函数是:
copy_from_user()
copy_to_user()
Linux设备读写返回值如果是负数,则表示出错。出错码定义在
linux/errno.h
Linux设备通过两个设备号来标识:
主设备号
从设备号
准备工作:
Linux设备/dev下有很多文件,不管以后你就设备是否存在,软件都会生成对应文件。创建/dev/xxx方式总共有:
静态设备创建;
使用设备文件系统和杂项框架;
静态设备创建,一般是由MAKEDEV(mknod程序)完成,也可以手动创建如:
mknod /dev/mydev c 202 108 //备注:主设备号202 从设备号108
本文将通过ioctl_test应用程序和helloworld驱动调试。
在内核中,cdev数据结构表示一个字符类型的设备,该数据结构用于将设备注册到系统中。
字符设备的注册/注销是通过主从设备号来实现。dev_t用来保存设备的标识信息(主从设备号),主从设备号通过MKDEV宏来获取.
静态分配和释放一个设备标识:
#include
unregister_chrdev_region()
动态分配和释放一个设备标识:
分配一组字符设备编号,
alloc_chrdev_region()
分配完字符设备之后,
初始化字符设备:
cdev_init()
字符设备注册到内核:
cdev_add()
通过
/proc/devices
#include
#include
#include
#define MY_MAJOR_NUM 202 //主设备号static struct cdev my_dev;static int my_dev_open(struct inode *inode, struct file *file)
{pr_info("my_dev_open() is called.\n");return 0;
}static int my_dev_close(struct inode *inode, struct file *file)
{pr_info("my_dev_close() is called.\n");return 0;
}static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);return 0;
}/* declare a file_operations structure */
//创建一个my_dev_fops的文件file_operations数据结构体
//定义:
// *打开
// *读取
// *写入
static const struct file_operations my_dev_fops = {.owner = THIS_MODULE,.open = my_dev_open,.release = my_dev_close,.unlocked_ioctl = my_dev_ioctl,
};static int __init hello_init(void)
{int ret;dev_t dev &#61; MKDEV(MY_MAJOR_NUM, 0);pr_info("Hello world init\n");/* Allocate device numbers */ret &#61; register_chrdev_region(dev, 1, "my_char_device");if (ret < 0){pr_info("Unable to allocate mayor number %d\n", MY_MAJOR_NUM);return ret;}/* Initialize the cdev structure and add it to the kernel space */cdev_init(&my_dev, &my_dev_fops);ret&#61; cdev_add(&my_dev, dev, 1);if (ret < 0){unregister_chrdev_region(dev, 1);pr_info("Unable to add cdev\n");return ret;}return 0;
}static void __exit hello_exit(void)
{pr_info("Hello world exit\n");cdev_del(&my_dev);unregister_chrdev_region(MKDEV(MY_MAJOR_NUM, 0), 1);
}module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is a module that interacts with the ioctl system call");
obj-m &#43;&#61; helloworld_sam_char_driver.oKERNEL_DIR ?&#61; $(HOME)/my-linux-samall:make -C $(KERNEL_DIR) \ARCH&#61;arm CROSS_COMPILE&#61;arm-poky-linux-gnueabi- \SUBDIRS&#61;$(PWD) modulesclean:make -C $(KERNEL_DIR) \ARCH&#61;arm CROSS_COMPILE&#61;arm-poky-linux-gnueabi- \SUBDIRS&#61;$(PWD) cleandeploy:scp *.ko root&#64;10.0.0.10:
/** A simple application to call helloworld character driver&#39;s ioctl.**/
#include
#include
#include
#include
{/* First you need run "mknod /dev/mydev c 202 0" to create /dev/mydev */int my_dev &#61; open("/dev/mydev", 0);if (my_dev < 0) {perror("Fail to open device file: /dev/mydev.");} else {ioctl(my_dev, 100, 110); // cmd &#61; 100, arg &#61; 110.close(my_dev);}return 0;
}
insmod helloworld_char_driver.ko
cat /proc/devices //查看申请的主设备号202 "my_char_device"
ls -l /dev
mknod /dev/mydev c 202 0 //在/dev下&#xff0c;创建mydev
./ioctl_test
rmmod helloworld_char_driver.ko
驱动构建成为可加载的内核模块。将驱动作为内核代码树的一部分构建到内核的二进制镜像中。在内核目录新增helloworld_char_driver.c&#xff1a;
/drivers/char/
编辑~/my-linux-imx/drivers/char/Kconfig&#xff0c;并在末尾添加&#xff1a;
config HELLOWORLDtristate "My helloworld driver"default nhelpThe simplest driver.
编辑~/my-linux-imx/drivers/char/Makefile&#xff0c;并在末尾添加&#xff1a;
obj-$(CONFIG_HELLOWORLD) &#43;&#61; helloworld_char_driver.o
修改完Kconfig和Makefile之后&#xff0c;helloworld_char_driver将成为内核的一部分&#xff0c;而不是一个可加载模块。
接下来是构建内核镜像步骤&#xff1a;
打开menuconfig窗口&#xff0c;选择main munu -> Device Driver -> Character devices -> My simple helloworld driver 选择*&#xff0c;然后保存退出GUI.
检查&#xff1a;
make menuconfig ARCH&#61;arm
打开内核根目录的.config文件将看到CONFIG_HELLOWORLD符号以及被添加进去了。
编译新镜像&#xff1a;
source /opt/fsl-imx-x11/4.9.11-1.0.0/environment-setup-cortexa7hf
make zImage
cp /arch/arm/boot/zImage /var/lib/tftpboot/
测试调试&#xff0c;启动系统后查看&#xff1a;
cat /proc/devices
mknod /dev/mydev c 202 0 //申请主设备号
./ioctl_test
在Linux2.6之后&#xff0c;增加了sysfs即虚拟文件系统。sysfs的任务是方便用户态使用查看系统硬件。sysfs是通过Linux内核配置CONFIG_SYSFS来打开并准备的。内核通过设备文件系统创建设备之后&#xff0c;会给udevd发送一个uevent.
在/sys/class目录下&#xff0c;有一个类别名&#xff0c;而每个设备都有一个设备名。
当驱动register_chrdev_region()函数执行之后&#xff0c;并没有在/class/sys/生成设备文件&#xff0c;而是使用如下&#xff1a;
驱动使用内核API创建/销毁类别&#xff1a;
#include
class_destroy()
创建设备节点内核API&#xff1a;
#include
device_destory()
helloworld_class_driver.c代码&#xff1a;
#include
#include
#include
#include
#define CLASS_NAME "hello_class"static struct class* helloClass;
static struct cdev my_dev;
dev_t dev;static int my_dev_open(struct inode *inode, struct file *file)
{pr_info("my_dev_open() is called.\n");return 0;
}static int my_dev_close(struct inode *inode, struct file *file)
{pr_info("my_dev_close() is called.\n");return 0;
}static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{pr_info("my_dev_ioctl() is called. cmd &#61; %d, arg &#61; %ld\n", cmd, arg);return 0;
}/* declare a file_operations structure */
static const struct file_operations my_dev_fops &#61; {.owner &#61; THIS_MODULE,.open &#61; my_dev_open,.release &#61; my_dev_close,.unlocked_ioctl &#61; my_dev_ioctl,
};static int __init hello_init(void)
{int ret;dev_t dev_no;int Major;struct device* helloDevice;pr_info("Hello world init\n");/* Allocate dynamically device numbers */ret &#61; alloc_chrdev_region(&dev_no, 0, 1, DEVICE_NAME);if (ret < 0){pr_info("Unable to allocate Mayor number \n");return ret;}/* Get the device identifiers */Major &#61; MAJOR(dev_no);dev &#61; MKDEV(Major,0);pr_info("Allocated correctly with major number %d\n", Major);/* Initialize the cdev structure and add it to the kernel space */cdev_init(&my_dev, &my_dev_fops);ret &#61; cdev_add(&my_dev, dev, 1);if (ret < 0){unregister_chrdev_region(dev, 1);pr_info("Unable to add cdev\n");return ret;}/* Register the device class */helloClass &#61; class_create(THIS_MODULE, CLASS_NAME);if (IS_ERR(helloClass)){unregister_chrdev_region(dev, 1);cdev_del(&my_dev);pr_info("Failed to register device class\n");return PTR_ERR(helloClass);}pr_info("device class registered correctly\n");/* Create a device node named DEVICE_NAME associated a dev */helloDevice &#61; device_create(helloClass, NULL, dev, NULL, DEVICE_NAME);if (IS_ERR(helloDevice)){class_destroy(helloClass);cdev_del(&my_dev);unregister_chrdev_region(dev, 1);pr_info("Failed to create the device\n");return PTR_ERR(helloDevice);}pr_info("The device is created correctly\n");return 0;
}static void __exit hello_exit(void)
{device_destroy(helloClass, dev); /* remove the device */class_destroy(helloClass); /* remove the device class */cdev_del(&my_dev);unregister_chrdev_region(dev, 1); /* unregister the device numbers */pr_info("Hello world with parameter exit\n");
}module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is a module that interacts with the ioctl system call");
测试调试&#xff1a;
insmod helloworld_class_driver.ko //加载模块
ls /sys/class/
ls /sys/class/hello_class/my_dev
ls -l /dev
./ioctl_test
rmmod helloworld_clsss_driver.ko
Linux允许模块注册其各自的从设备编号。官方分配给杂项驱动的主设备编号是10。
杂项设备文件创建目录如下&#xff1a;
/sys/class/misc
注册杂项设备编号&#xff1a;
include/linux/miscdevice.h
杂项驱动导出的两个函数&#xff1a;
#include
misc_register()
misc_deregister()
misc_sam_driver.c代码&#xff1a;
#include
#include
#include
{pr_info("my_dev_open() is called.\n");return 0;
}static int my_dev_close(struct inode *inode, struct file *file)
{pr_info("my_dev_close() is called.\n");return 0;
}static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{pr_info("my_dev_ioctl() is called. cmd &#61; %d, arg &#61; %ld\n", cmd, arg);return 0;
}static const struct file_operations my_dev_fops &#61; {.owner &#61; THIS_MODULE,.open &#61; my_dev_open,.release &#61; my_dev_close,.unlocked_ioctl &#61; my_dev_ioctl,
};/* declare & initialize struct miscdevice */
static struct miscdevice helloworld_miscdevice &#61; {.minor &#61; MISC_DYNAMIC_MINOR,.name &#61; "mydev",.fops &#61; &my_dev_fops,
};static int __init hello_init(void)
{int ret_val;pr_info("Hello world init\n");/* Register the device with the Kernel */ret_val &#61; misc_register(&helloworld_miscdevice);if (ret_val !&#61; 0) {pr_err("could not register the misc device mydev");return ret_val;}pr_info("mydev: got minor %i\n",helloworld_miscdevice.minor);return 0;
}static void __exit hello_exit(void)
{pr_info("Hello world exit\n");/* unregister the device with the Kernel */misc_deregister(&helloworld_miscdevice);}module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is the helloworld_char_driver using misc framework");
测试调试&#xff1a;
insmod misc_sam_driver.ko
ls /sys/class/misc
ls /sys/class/misc/mydev/
ls /sys/class/misc/mydev/dev
./ioctl_test
rmmod misc_sam_driver.ko
感谢阅读&#xff0c;祝君成功&#xff01;
-by aiziyou