LDD学习2--Scull(TODO)
1 代码准备
之前我是用的老版的代码,下面这个位置:
examples / Linux Device Drivers 3rd Edition · GitLab
代码确实是原汁原味,但是遇到的问题就是没法编译:
海量的报错把我搞得有点懵圈,后面查了下,要用新版发烧友们的代码:
git clone https://github.com/martinezjavier/ldd3.git
这个弄下来一下就可以了。
在ubuntu的x86设备上是直接可以编过。
在树莓派上面整体编译还是有点问题:
会说有一个sys/io.h找不到。
查了一下是因为这个头文件特供x86平台。ARM这些直接就是不过。
树莓派(以及其他 ARM 架构)不支持
sys/io.h
中的直接 I/O 端口操作,因为这是为 x86 架构设计的。你需要检查代码,找出使用了sys/io.h
中函数的部分,并移除或替换为 ARM 兼容的操作。如果你使用的是
inp
,outp
,inb
,outb
等直接访问 I/O 端口的函数,它们通常只在 x86 系统上使用。在 ARM 设备上,访问外设应该通过内存映射(memory-mapped I/O)或者通过其他 ARM 专用的机制来完成。
好吧,不过不重要,这次毕竟是看scull,这个单独可以编过。就无所谓了。
试了下insmod,也是马上就能出效果。
然后就可以mknod来操作了,这里都要手动去mknod。对了,要chmod和sudo。。。
也可以参考它自己带的脚本
#!/bin/bash
# Sample init script for the scull driver module <rubini@linux.it>
DEVICE="scull"
SECTION="misc"
# The list of filenames and minor numbers: $PREFIX is prefixed to all names
PREFIX="scull"
FILES=" 0 0 1 1 2 2 3 3 priv 16
pipe0 32 pipe1 33 pipe2 34 pipe3 35
single 48 uid 64 wuid 80"
INSMOD=/sbin/insmod; # use /sbin/modprobe if you prefer
function device_specific_post_load () {
true; # fill at will
}
function device_specific_pre_unload () {
true; # fill at will
}
# Everything below this line should work unchanged for any char device.
# Obviously, however, no options on the command line: either in
# /etc/${DEVICE}.conf or /etc/modules.conf (if modprobe is used)
# Optional configuration file: format is
# owner <ownername>
# group <groupname>
# mode <modename>
# options <insmod options>
CFG=/etc/${DEVICE}.conf
# kernel version, used to look for modules
KERNEL=`uname -r`
#FIXME: it looks like there is no misc section. Where should it be?
MODDIR="/lib/modules/${KERNEL}/kernel/drivers/${SECTION}"
if [ ! -d $MODDIR ]; then MODDIR="/lib/modules/${KERNEL}/${SECTION}"; fi
# Root or die
if [ "$(id -u)" != "0" ]
then
echo "You must be root to load or unload kernel modules"
exit 1
fi
# Read configuration file
if [ -r $CFG ]; then
OWNER=`awk "\\$1==\"owner\" {print \\$2}" $CFG`
GROUP=`awk "\\$1==\"group\" {print \\$2}" $CFG`
MODE=`awk "\\$1==\"mode\" {print \\$2}" $CFG`
# The options string may include extra blanks or only blanks
OPTIONS=`sed -n '/^options / s/options //p' $CFG`
fi
# Create device files
function create_files () {
cd /dev
local devlist=""
local file
while true; do
if [ $# -lt 2 ]; then break; fi
file="${DEVICE}$1"
mknod $file c $MAJOR $2
devlist="$devlist $file"
shift 2
done
if [ -n "$OWNER" ]; then chown $OWNER $devlist; fi
if [ -n "$GROUP" ]; then chgrp $GROUP $devlist; fi
if [ -n "$MODE" ]; then chmod $MODE $devlist; fi
}
# Remove device files
function remove_files () {
cd /dev
local devlist=""
local file
while true; do
if [ $# -lt 2 ]; then break; fi
file="${DEVICE}$1"
devlist="$devlist $file"
shift 2
done
rm -f $devlist
}
# Load and create files
function load_device () {
if [ -f $MODDIR/$DEVICE.ko ]; then
devpath=$MODDIR/$DEVICE.ko
else if [ -f ./$DEVICE.ko ]; then
devpath=./$DEVICE.ko
else
devpath=$DEVICE; # let insmod/modprobe guess
fi; fi
if [ "$devpath" != "$DEVICE" ]; then
echo -n " (loading file $devpath)"
fi
if $INSMOD $devpath $OPTIONS; then
MAJOR=`awk "\\$2==\"$DEVICE\" {print \\$1}" /proc/devices`
remove_files $FILES
create_files $FILES
device_specific_post_load
else
echo " FAILED!"
fi
}
# Unload and remove files
function unload_device () {
device_specific_pre_unload
/sbin/rmmod $DEVICE
remove_files $FILES
}
case "$1" in
start)
echo -n "Loading $DEVICE"
load_device
echo "."
;;
stop)
echo -n "Unloading $DEVICE"
unload_device
echo "."
;;
force-reload|restart)
echo -n "Reloading $DEVICE"
unload_device
load_device
echo "."
;;
*)
echo "Usage: $0 {start|stop|restart|force-reload}"
exit 1
esac
exit 0
2 看看代码
scull 使用一块虚拟内存作为模拟设备,并将这块内存划分成多个设备。例如,程序中可以定义 4 个 scull 设备,每个设备都有一个独立的内存区域。每个区域的大小是通过编译时参数或模块参数指定的。
内存分配: scull 的内存分配是通过一个链表实现的,每个链表节点保存了一块内存数据。链表中每个节点的大小是固定的,称为 quantum,多个 quantum 组成一个 qset。当数据超过当前节点的 quantum 大小时,scull 会自动分配新的节点并将其连接到链表中,形成一个灵活的内存管理机制。
核心结构
struct scull_dev
struct scull_dev {
struct scull_qset *data; /* Pointer to first quantum set */
int quantum; /* the current quantum size */
int qset; /* the current array size */
unsigned long size; /* amount of data stored here */
struct semaphore sem; /* Mutual exclusion semaphore */
struct cdev cdev; /* Char device structure */
};
scull_qset
struct scull_qset {
void **data;
struct scull_qset *next;
};
scull_init_module
这个函数是初始化的函数,
int scull_init_module(void)
{
int result, i;
dev_t dev = 0;
/*
* Get a range of minor numbers to work with, asking for a dynamic
* major unless directed otherwise at load time.
*/
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
"scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}
/*
* allocate the devices -- we can't have them static, as the number
* can be specified at load time
*/
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_devices) {
result = -ENOMEM;
goto fail; /* Make this more graceful */
}
memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
/* Initialize each device. */
for (i = 0; i < scull_nr_devs; i++) {
scull_devices[i].quantum = scull_quantum;
scull_devices[i].qset = scull_qset;
mutex_init(&scull_devices[i].lock);
scull_setup_cdev(&scull_devices[i], i);
}
/* At this point call the init function for any friend device */
dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
dev += scull_p_init(dev);
dev += scull_access_init(dev);
#ifdef SCULL_DEBUG /* only when debugging */
scull_create_proc();
#endif
return 0; /* succeed */
fail:
scull_cleanup_module();
return result;
}
可以看到,初始化的操作就是几个,alloc_chrdev_region,这个用来动态注册设备,kmalloc,这个给自己的主结构分配内存,然后初始化了四个次设备(TODO,这部分还要看看)。另外两个友设备这次就先不看了。。
这里说说主次设备号的区别
主设备号(Major Number):
- 主设备号用于标识设备驱动程序。内核通过主设备号来确定哪个驱动程序会处理对特定设备的请求。
- 每个设备驱动程序通常会有一个唯一的主设备号,或者一组主设备号,用于它们管理的所有设备。
- 主设备号通常在驱动程序注册时分配,可以是静态分配的,也可以是动态分配的。
次设备号(Minor Number):
- 次设备号用于标识属于同一驱动程序管理的特定设备实例。它允许内核区分同一个驱动程序管理的多个设备。
- 次设备号可以看作是驱动程序内部的索引,用于区分同一类型的不同设备,如第一个设备、第二个设备等。
- 次设备号的分配通常由驱动程序控制,可以根据设备的数量和特性来分配。
简单来说,主设备号就是什么设备对应什么驱动,次设备号就是一个驱动可以多个设备。
scull_cleanup_module
这个是退出的清理函数
/*
* The cleanup function is used to handle initialization failures as well.
* Thefore, it must be careful to work correctly even if some of the items
* have not been initialized
*/
void scull_cleanup_module(void)
{
int i;
dev_t devno = MKDEV(scull_major, scull_minor);
/* Get rid of our char dev entries */
if (scull_devices) {
for (i = 0; i < scull_nr_devs; i++) {
scull_trim(scull_devices + i);
cdev_del(&scull_devices[i].cdev);
}
kfree(scull_devices);
}
#ifdef SCULL_DEBUG /* use proc only if debugging */
scull_remove_proc();
#endif
/* cleanup_module is never called if registering failed */
unregister_chrdev_region(devno, scull_nr_devs);
/* and call the cleanup functions for friend devices */
scull_p_cleanup();
scull_access_cleanup();
}
然后就是注册回调:
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.unlocked_ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
scull_trim():
会在scull_cleanup_module中调用,释放设备占用的内存。 当设备被关闭时调用此函数,它会遍历链表并释放所有分配的内存。
scull_open():
打开设备。 在设备文件被打开时调用,增加设备的引用计数,并进行必要的初始化。
scull_release():
释放设备。 当设备文件被关闭时调用,减少引用计数,如果没有进程再使用该设备,则释放其资源。
scull_read():
从设备读取数据。 将内存中的数据拷贝到用户空间。
scull_write():
向设备写入数据。 将用户空间的数据拷贝到设备的内存中,必要时扩展内存区域。
重点看一下read和write两个函数
scull_read
ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;/* the first listitem */
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset; /* how many bytes in the listitem */
int item, s_pos, q_pos, rest;
ssize_t retval = 0;
if (mutex_lock_interruptible(&dev->lock))
return -ERESTARTSYS;
if (*f_pos >= dev->size)
goto out;
if (*f_pos + count > dev->size)
count = dev->size - *f_pos;
/* find listitem, qset index, and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
/* follow the list up to the right position (defined elsewhere) */
dptr = scull_follow(dev, item);
if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
goto out; /* don't fill holes */
/* read only up to the end of this quantum */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
mutex_unlock(&dev->lock);
return retval;
}
比较典型的就是mutex_lock_interruptible和mutex_unlock进行锁操作。mutex_lock_interruptible是获得锁,但是可以被信号打断。。
之后就是根据偏移找到正确的数据,并拷贝到用户空间。
scull_write
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t retval = -ENOMEM; /* value used in "goto out" statements */
if (mutex_lock_interruptible(&dev->lock))
return -ERESTARTSYS;
/* find listitem, qset index and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
/* follow the list up to the right position */
dptr = scull_follow(dev, item);
if (dptr == NULL)
goto out;
if (!dptr->data) {
dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
if (!dptr->data)
goto out;
memset(dptr->data, 0, qset * sizeof(char *));
}
if (!dptr->data[s_pos]) {
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if (!dptr->data[s_pos])
goto out;
}
/* write only up to the end of this quantum */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
/* update the size */
if (dev->size < *f_pos)
dev->size = *f_pos;
out:
mutex_unlock(&dev->lock);
return retval;
}
除了锁机制,然后将用户空间传来的数据拷贝给链表。
scull_ioctl主要是设置参数,这里就不细看了。
《Linux Device Drivers》(LDD)书籍中的 `scull`(Simple Character Utility for Loading Localities)是一个用于演示 Linux 字符设备驱动程序编写的示例代码。它为理解 Linux 内核模块和字符设备驱动程序的编写提供了基础实践平台,帮助开发者了解 Linux 内核中字符设备的工作原理。
### `scull` 的主要作用
`scull` 模块创建了一个虚拟的字符设备,它并不和真实的硬件设备交互,而是将分配的内存空间作为设备存储,这样开发者可以在内存中操作数据,模拟实际的字符设备工作流程。通过这个虚拟设备,LDD 的读者可以学习字符设备驱动的基本结构和关键操作,如文件的打开、关闭、读写、IO 控制(ioctl)等。### `scull` 的关键功能和特点
1. **字符设备驱动**:
`scull` 是一个字符设备驱动程序。字符设备(Character Device)是一种可以顺序读取和写入数据的设备类型,与块设备不同,它没有固定的块大小。2. **虚拟设备**:
`scull` 并不和实际的硬件设备交互,而是在内存中分配一个区域作为虚拟设备。这个区域类似于环形缓冲区或者文件,用户可以通过系统调用 `read()` 和 `write()` 来从该设备中读取和向设备写入数据。3. **多种操作支持**:
`scull` 支持字符设备常见的操作,包括:
- **打开/关闭设备** (`open`, `release`)。
- **读取数据** (`read`)。
- **写入数据** (`write`)。
- **IO 控制命令** (`ioctl`):`scull` 中的 ioctl 函数实现了一些控制命令,用于改变设备的行为。
- **内存管理**:`scull` 还展示了如何实现 `mmap` 来映射设备内存到用户空间。4. **不同类型的 `scull` 设备**:
`scull` 提供了不同的变种,展示了内核模块的灵活性:
- **scull0**:标准的字符设备。
- **scullpipe**:模拟管道设备。
- **scullsingle**:只能单用户打开的设备。
- **sculluid**:每个用户独占的设备。5. **内存管理**:
`scull` 在设备打开时分配内存,并通过不同的方式管理内存。开发者可以学习如何动态分配和释放内存、如何处理内存映射等操作。6. **并发处理**:
`scull` 的代码中展示了如何处理多个进程对同一个设备的并发访问问题,比如如何使用内核中的 **互斥锁**、**信号量** 等同步机制,确保设备操作的安全性。### `scull` 的实现流程
1. **设备的注册和初始化**:
`scull` 模块加载时,注册了一个字符设备驱动,定义了字符设备的主设备号和次设备号。在模块的 `init` 函数中调用 `register_chrdev_region()` 或 `alloc_chrdev_region()` 来动态分配设备号。2. **文件操作接口**:
`scull` 实现了 `file_operations` 结构体中的关键函数,注册到内核中,用户程序通过 `open`, `read`, `write`, `close` 等系统调用与设备交互。这些操作由 `scull_open`, `scull_release`, `scull_read`, `scull_write` 函数实现。3. **数据读写**:
读操作会从内存中读取指定的字节数,写操作则将数据写入到分配的内存中。这个过程模拟了实际硬件设备的数据读写行为。4. **IOCTL 控制**:
`ioctl` 函数允许用户空间程序发送控制命令给内核模块,改变设备的行为。这部分通常用于设备的特殊功能控制,`scull` 中展示了如何处理和定义这些控制命令。5. **模块的卸载**:
`scull` 模块被卸载时,调用 `unregister_chrdev_region()` 函数注销设备号,释放设备分配的内存。### 使用 `scull` 示例的意义
通过 `scull`,开发者可以学习并掌握以下内容:
- 字符设备驱动的开发流程。
- 如何处理内核中的文件操作(如 `open`, `read`, `write`, `ioctl` 等)。
- 如何管理内核空间的内存(动态分配和释放)。
- 如何处理并发访问问题。
- 如何使用内核日志系统(如 `printk()`)进行调试。
- 模块的加载与卸载过程。### 总结
`scull` 是 LDD 中用于教学的字符设备驱动程序,虽然它是一个虚拟设备,但它涵盖了字符设备驱动开发的方方面面。通过 `scull`,读者可以掌握编写 Linux 字符设备驱动程序的核心技能,并能够理解设备驱动程序在内核中的基本原理和结构。
原文地址:https://blog.csdn.net/fanged/article/details/142408723
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!