1. 程式人生 > >Linux裝置驅動模組自載入示例與原理解析

Linux裝置驅動模組自載入示例與原理解析

本文介紹Linux裝置驅動模組在設備註冊時如何實現自動載入和建立裝置節點。
在Linux系統中,基於sysfs檔案系統、裝置驅動模型和udev工具可以實現在裝置模組“冷、熱”載入時自動載入裝置對應的驅動程式,同時可以按需在/dev目錄下建立裝置節點。
本文中我搭建好環境並寫了兩個簡單的示例程式demo_device.c和device_driver.c來模擬“裝置”與“驅動”的自動載入和裝置節點自動建立的過程。最後通過核心原始碼來理解其中的原理知識。
實驗環境:
核心版本:Linux-3.12.35
工具:(1)udev:udev-137
            (2)交叉編譯工具:arm-bcm2708-linux-gnueabi-
示例環境:(1)宿主機:x86(CentOS6.6)
            (2)單板:樹莓派b

一、示例演示
1、準備工作(環境搭建)

(1)首先配置核心,確認其支援NETLINK和inotify:

[*] Networking support --->

Networking options  --->

                   <*> Unixdomain sockets

File systems  --->

[*] Inotify support for userspace  

udev使用inotify機制監測udev的規則檔案是否發生變化,udev和核心驅動模組之間的uevent互動使用socket,需要核心支援NETLINK。
(2)檔案系統支援proc和sysfs檔案系統

udev需要sysfs檔案系統的支援,同時後續用到的lsmod命令是通過proc和sysfs檔案系統獲取核心模組資訊的,所以需要支援proc和sysfs,最簡單的做法可以在init指令碼中新增:

mount -n -t proc proc/proc

mount -n -t sysfssysfs /sys

(3)檔案系統中安裝有udevd、udevadm和驅動載入規則檔案

[[email protected]]$ ls initramfs/lib/udev/rules.d/

80-drivers.rules

[[email protected] raspberry]$ lsinitramfs/bin/udev*

initramfs/bin/udevadm initramfs/bin/udevd

80-drivers.rules是載入驅動的規則,udevadm用於對冷插拔裝置模擬熱插拔,udevd是udev守護程式,實時監測核心向用戶傳送的uevent以及rules的變更。
2、示例程式demo_device.c和device_driver.c的原始碼如下:

demo_device.c:

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/io.h>

/* 平臺裝置demo_device */
static struct platform_device demo_device = {
	.name		= "demo_device",
	.id		    = -1,
};

/* 模組初始化函式 */
static int __init demo_device_init(void)
{ 
	/* 註冊平臺裝置 */
	printk(KERN_INFO "Demo Device Register\n");
    platform_device_register(&demo_device);
    return 0;
}

/* 模組去初始化函式 */
static void __exit demo_device_exit(void)
{
	/* 登出平臺裝置 */
	printk(KERN_INFO "Demo Device UnRegister\n");
    platform_device_unregister(&demo_device);
}

module_init(demo_device_init);
module_exit(demo_device_exit);


MODULE_LICENSE("GPL"); 
由於嵌入式裝置驅動較多使用Platform匯流排,所以這裡的demo_device使用Platform匯流排(其他實體匯流排如PCI、USB等原理類似)。這裡第14行的init函式負責在載入模組時註冊demo_device裝置,第21行函式負責在模組解除安裝時登出demo_device裝置。第10行的name用於platform裝置與驅動的匹配。

demo_driver.c:

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/kmod.h>
#include <linux/of_platform.h>

#define DEVICE_NAME     "demo_dev"

static struct cdev demo_dev;
static dev_t ndev;
static struct class *demo_class = NULL;

/* 字元裝置read介面 */
static ssize_t demo_read (struct file *filp, char __user *buf, size_t sz, loff_t *off)
{
	printk(KERN_INFO "Demo Dev Read\n");
	return 0;
}

/* 字元裝置open介面 */
static int demo_open(struct inode *nd, struct file *filp)
{
	printk(KERN_INFO "Demo Dev Open\n");
	return 0;
}

/* 字元裝置close介面 */
static int demo_close (struct inode *nd, struct file *filp)
{
	printk(KERN_INFO "Demo Dev Close\n");
	return 0;
}

struct file_operations demo_ops =
{
	.owner = THIS_MODULE,
	.open = demo_open,
	.release = demo_close,
	.read = demo_read,
};

/* 平臺驅動probe介面 */
static int demo_driver_probe(struct platform_device *pdev)
{
	int ret;
	
	/* 註冊字元裝置 */
	cdev_init(&demo_dev, &demo_ops);
	
	ret = alloc_chrdev_region(&ndev, 0, 1, DEVICE_NAME);
	if(0 > ret){
		printk(KERN_INFO "Alloc Chrdev Region Fail\n");
		return ret;
	}
	
	printk(KERN_INFO "Demo Driver: Major=%d Minor=%d Cdev Register\n", 
														MAJOR(ndev), MINOR(ndev));
	
	ret = cdev_add(&demo_dev, ndev, 1);
	if(0 > ret){
		printk(KERN_INFO "Cdev Add Fail\n");
		return ret;		
	}
	
	/* (建立裝置節點) */
	device_create(demo_class, NULL, ndev, NULL, DEVICE_NAME);
	
	return 0;
}

/* 平臺驅動remove介面 */
static int demo_driver_remove(struct platform_device *pdev)
{
	printk(KERN_INFO "Demo Driver: Cdev UnRegister\n");
	
	device_destroy(demo_class, ndev);
	cdev_del(&demo_dev);
	unregister_chrdev_region(ndev, 1);
	
	return 0;
}

/* 平臺驅動demo_driver */
static struct platform_driver demo_driver = {
	.driver	= {
		.name    = "demo_device",   
		.owner	 = THIS_MODULE,
	},
	.probe   = demo_driver_probe,
	.remove  = demo_driver_remove,
};

/* 模組初始化函式 */
static int __init demo_driver_init(void)
{ 
	/* 建立裝置類demo_dev */
	demo_class = class_create(THIS_MODULE, DEVICE_NAME);
	if(IS_ERR(demo_class))
	{
		printk(KERN_INFO "Class Create Fail\n");
		return -1;		
	}
	
	/* 載入平臺驅動 */
	printk(KERN_INFO "Demo Driver Register\n");
    platform_driver_register(&demo_driver);
	
    return 0;
}

/* 模組去初始化函式 */
static void __exit demo_driver_exit(void)
{
	/* 登出裝置類demo_dev */
	class_destroy(demo_class);
	
	/* 登出平臺驅動 */
	printk(KERN_INFO "Demo Driver UnRegister\n");
    platform_driver_unregister(&demo_driver);
}


module_init(demo_driver_init);
module_exit(demo_driver_exit);


MODULE_LICENSE("GPL");  
MODULE_ALIAS("platform:demo_device");

第89行的init函式用於在載入驅動模組時註冊demo_driver驅動,同時建立demo_class(用於自動生成裝置節點),第104行的exit函式用於在解除安裝驅動模組時登出demo_driver驅動;第82行的name和demo_device中的一樣,用於和platform裝置進行匹配;第44行的probe函式在裝置和驅動匹配後會執行,它的主要工作是像核心新增字元裝置cdev,同時呼叫device_create()函式建立裝置節點;第18、24和30行是標準的檔案操作介面,這裡就不詳細介紹了。第118行指定了驅動模組的驅動別名,後面詳細介紹它的作用。

3、編譯與安裝demo_device和demo_driver

編譯使用的Makefile如下:

ifneq ($(KERNELRELEASE),)

obj-m := demo_device.o demo_driver.o

else
	
KDIR := /home/apple/raspberry/build/linux-rpi-3.12.y
all:
	make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-bcm2708-linux-gnueabi-
modules_install:
	make -C $(KDIR) M=$(PWD) modules_install ARCH=arm CROSS_COMPILE=arm-bcm2708-linux-gnueabi-
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers  modul*

endif
其中KDIR指向的是核心原始碼目錄,然後編譯與安裝:
[[email protected] udev_test]$ ls
demo_device.c  demo_driver.c Makefile
[[email protected] udev_test]$ make
[[email protected] udev_test]$ make modules_install  INSTALL_MOD_PATH=/home/apple/raspberry/sysroot/kernel
其中INSTALL_MOD_PATH是核心安裝目錄,編譯與安裝完成後,會將編譯生成的demo_device.ko和demo_driver.ko兩個檔案拷貝到安裝目錄下的lib/modules/3.12.35/extra/中;
[[email protected] extra]$ ls~/raspberry/sysroot/kernel/lib/modules/3.12.35/extra/
demo_device.ko  demo_driver.ko
同時安裝module時會呼叫depmod工具,更新lib/modules/3.12.35/目錄下的modules.alias、modules.alias.bin、modules.dep和modules.dep.bin檔案。其中modules.dep和modules.dep.bin記錄了模組的依賴關係,在呼叫modprobe命令載入模組時會用到他(們),由於這裡的兩個demo模組並不依賴於其他模組,所以他們的依賴為空:
extra/demo_driver.ko:
extra/demo_device.ko:
其中modules.alias、modules.alias.bin記錄了驅動模組中記錄的驅動別名,在自動載入驅動程式是會用到他(們),檢視modules.alias檔案中已經新增上了剛才安裝的demo_device:
alias platform:demo_devicedemo_driver
也可以通過modinfo命令來檢視demo_driver.ko驅動模組的驅動別名:
[[email protected] extra]$ modinfo -F alias demo_driver.ko
platform:demo_device
4、手動載入demo_device.ko時自動載入demo_driver.ko並建立裝置節點
首先將demo_device.ko、demo_driver.ko、modules.alias.bin和modules.dep.bin拷貝到目標單板的根檔案系統相應目錄下(路徑需要一致):
[[email protected]]$ cp modules/3.12.35/modules.alias.bin initramfs/lib/modules/3.12.35/
[[email protected]]$ cp modules/3.12.35/modules.dep.bin initramfs/lib/modules/3.12.35/
[[email protected] raspberry]$cp modules/3.12.35/extra/demo* initramfs/lib/modules/3.12.35/extra/
啟動單板系統,在udevd服務程序啟動後手動載入demo_device.ko模組(如果沒有啟動則使用命令“udevd –daemon”啟動):
bash-4.3# modprobe demo_device
[   68.634194] Demo Device Register
[   68.655775] Demo Driver Register
[   68.659129] Demo Driver: Major=248 Minor=0Cdev Register
從核心在終端的輸出可以看到不僅僅demo_device.ko被載入了,同時demo_driver.ko也被載入了,裝置和驅動正確的匹配上了,檢視/dev目錄下的裝置檔案也生成了:
bash-4.3# ls -l /dev/demo*
crw------- 1 0 0 248, 0Jan  1 00:00 /dev/demo_dev
bash-4.3# cat /dev/demo_dev
[  517.527372] Demo Dev Open
[  517.530122] Demo Dev Read
[  517.532878] Demo Dev Close
demo_dev的驅動介面能夠正常的被呼叫並執行。
5、啟動udev前“冷載入"demo_device裝置時自動載入demo_driver.ko並建立裝置節點
Linux中存在一些特別的裝置驅動,其裝置被編譯入核心,在核心啟動階段被載入;但是其驅動程式被編譯為模組,在系統啟動以後自動識別裝置的型別並載入對應的驅動程式,例如Linux發行版中的主硬碟儲存裝置(核心啟動時,PCI子系統初始化,列舉總線上的的裝置)。
這種情況在嵌入式裝置中倒是不常見,但其本質上就是在系統啟動udev服務程序前向核心註冊裝置但不載入驅動,在udev程序啟動後該如何為這些裝置匹配並載入驅動。這裡的關鍵就要靠udevadm工具了。
首先啟動單板系統,我這裡修改了啟動init指令碼,不啟動udevd服務程序。然後載入demo_devices.ko:
bash-4.3# modprobe demo_device
[   27.812482] Demo Device Register
可以看出在沒有啟動udevd服務程序的情況下,這裡僅僅載入了裝置模組。接下來啟動udev服務程序:
bash-4.3# udevd --daemo
 [  454.385623] udevd[47]: starting version 173
啟動完udev守護程序後,udev就可以監聽來自核心的uevent事件了,接下來該udevadm出場了:
bash-4.3# udevadm trigger --action=add
[  645.329345] Demo Driver Register
[  645.470937] Demo Driver: Major=248 Minor=0Cdev Register
bash-4.3# ls -l /dev/demo*
crw------- 1 0 0 248, 0Jan  1 00:10 /dev/demo_dev
bash-4.3# cat /dev/demo_dev
[  713.227184] Demo Dev Open
[  713.229928] Demo Dev Read
[  713.232678] Demo Dev Close

在執行“udevadm trigger --action=add”命令後,驅動程式同前面“熱”載入裝置一樣,被自動的識別、載入和匹配上了。

二、原理解析

         裝置驅動“自動載入匹配”的根本是基於Linux裝置驅動模型,需要sysfs檔案系統和udev工具的支援。其主要流程如下圖所示:

   

要分析它的原理還是先從示例程式demo_device.c切入,檢視設備註冊後核心做了哪些工作,如何向用戶空間傳送註冊訊息;然後來分析udev接收該訊息後又如何找到對應的驅動程式模組demo_driver.ko並載入它;最後分析驅動和裝置的繫結以及裝置節點(/dev/demo_device)是如何自動生成的。

1、demo_device裝置的註冊。

demo_device.c中將裝置掛載了Platform總線上,這是一根虛擬的匯流排,在核心啟動時呼叫platform_bus_init()進行初始化:

int __init platform_bus_init(void)
{
	int error;

	early_platform_cleanup();

	error = device_register(&platform_bus);
	if (error)
		return error;
	error =  bus_register(&platform_bus_type);
	if (error)
		device_unregister(&platform_bus);
	return error;
}
在demo_device模組的初始化函式中呼叫platform_device_register()註冊平臺裝置:
/**
 * platform_device_register - add a platform-level device
 * @pdev: platform device we're adding
 */
int platform_device_register(struct platform_device *pdev)
{
	device_initialize(&pdev->dev);
	arch_setup_pdev_archdata(pdev);
	return platform_device_add(pdev);
}

這裡主要關注device_initialize和platform_device_add,首先3965行的device_initialize初始化其中的device結構體,這裡只需要關注下面這兩句:

	dev->kobj.kset = devices_kset;
	kobject_init(&dev->kobj, &device_ktype);

這兩句初始化了device結構體的kset指標為devices_kset(用於觸發uevent),以及ktype指標為devices_ktype(用於sysfs檔案操作),這裡先記下,後面會用到。

回到platform_device_register函式第397行的platform_device_add(),它將demo_device結構註冊進核心,註冊的步驟如下:

	if (!pdev->dev.parent)
		pdev->dev.parent = &platform_bus;

	pdev->dev.bus = &platform_bus_type;

這裡將device結構的父裝置設為platform_bus(用於sysfs目錄結構),這個結構前面platform_bus_ini中已經看到過了,同時設定device結構的匯流排bus_type為platform_bus_type(用於匹配驅動程式)。

	case PLATFORM_DEVID_NONE:
		dev_set_name(&pdev->dev, "%s", pdev->name);
		break;

接下來設定devices結構的name為其父裝置的name,這裡就是“demo_device”

	ret = device_add(&pdev->dev);
	if (ret == 0)
		return ret;
接下來就到了最為關鍵的device_add,它完成了核心部分的註冊,包括在sysfs檔案系統中建立目錄和檔案、向用戶層傳送uevent訊息以及嘗試匹配驅動程式等功能,來看一下其中關心部分的程式碼:
	parent = get_device(dev->parent);
	kobj = get_device_parent(dev, parent);
	if (kobj)
		dev->kobj.parent = kobj;
......
	/* first, register with generic layer. */
	/* we require the name to be set before, and pass NULL */
	error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
	if (error)
		goto Error;

此處首先設定demo裝置kobject結構parent指標為它父裝置的的kobject,就是前面platform_bus的kobject。由於kobject在sysfs系統中的體現就是目錄(目錄名為kobject的name,父目錄就是parent->kobject的name),所以在kobject_add被呼叫後,sysfs檔案系統的/sys/devices/platform目錄下生成demo_device目錄:

bash-4.3# ls/sys/devices/platform | grep demo

demo_device

	error = device_create_file(dev, &dev_attr_uevent);
	if (error)
		goto attrError;
這裡會在/sys/devices/platform/demo_device/目錄下建立一個名為uevent的檔案,dev_attr_uevent是一個device_attribute結構體,它使用巨集生成,所以這裡比較難找,分別在core.c、device.h和sysfs.h中有:
static DEVICE_ATTR_RW(uevent);

#define DEVICE_ATTR_RW(_name) \
	struct device_attribute dev_attr_##_name = __ATTR_RW(_name)

#define __ATTR_RW(_name) __ATTR(_name, (S_IWUSR | S_IRUGO),		\
			 _name##_show, _name##_store)

#define __ATTR(_name, _mode, _show, _store) {				\
	.attr = {.name = __stringify(_name), .mode = _mode },		\
	.show	= _show,						\
	.store	= _store,						\
}

將這個巨集展開後形式如下:

struct device_attribute dev_attr_uevent = {
		.attr = {
			.name = “uevent”,
			.mode = S_IWUSR | S_IRUGO;
		}
		.show = uevent_show,
		.store = uevent_store,
};
注意,這裡的_uevent_store函式就是前面“冷”載入裝置後還能再次觸發uevent並載入驅動的關鍵所在。
device_create_file函式呼叫sysfs_create_file在sysfs檔案系統的demo裝置的kobject之時的目錄下(即/sys/devices/platform/demo_device/)建立了名為uevent的檔案:

bash-4.3# ls/sys/devices/platform/demo_device/ueve*

/sys/devices/platform/demo_device/uevent

	if (MAJOR(dev->devt)) {
		error = device_create_file(dev, &dev_attr_dev);
		if (error)
			goto ueventattrError;

		error = device_create_sys_dev_entry(dev);
		if (error)
			goto devtattrError;

		devtmpfs_create_node(dev);
	}

回到device_add函式中,這裡如果設定了裝置的主裝置號,則為該裝置在/sysfs/dev/char目錄下建立一個符號連結,同時如果核心啟用了devtmpfs則會在/dev目錄下建立裝置節點。顯然現在這個分支並不會走到,但是最終載入完驅動後是會走進去執行的。

	kobject_uevent(&dev->kobj, KOBJ_ADD);
	bus_probe_device(dev);
這裡kobject_uevent函式會向用戶空間傳送KOBJ_ADD型別的uevent訊息,執行如下流程:kobject_uevent(&dev->kobj, KOBJ_ADD)->kobject_uevent_env(kobj,action, NULL),進入kobject_uevent_env()函式後,執行如下主要流程:
	top_kobj = kobj;
	while (!top_kobj->kset && top_kobj->parent)
		top_kobj = top_kobj->parent;

	kset = top_kobj->kset;
	uevent_ops = kset->uevent_ops;
kobject_uevent_env()函式首先找到device->kobject頂層kobject的kset,然後獲取其中的uevent_ops,前面已經看到這裡的kest即devices_kset,devices_kset結構在核心初始化時呼叫devices_init(void)函式初始化並綁定了uevent_ops為device_uevent_ops:
static const struct kset_uevent_ops device_uevent_ops = {
	.filter =	dev_uevent_filter,
	.name =		dev_uevent_name,
	.uevent =	dev_uevent,
};

這裡的dev_uevent函式會往發向使用者空間的uevent訊息中新增和裝置驅動相關的變數。繼續往下分析:

	/* environment buffer */
	env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
	if (!env)
		return -ENOMEM;

......

	/* default keys */
	retval = add_uevent_var(env, "ACTION=%s", action_string);
	if (retval)
		goto exit;
	retval = add_uevent_var(env, "DEVPATH=%s", devpath);
	if (retval)
		goto exit;
	retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
	if (retval)
		goto exit;

kobject_uevent_env()函式接下來為uevent建立分配buffer,然後往其中新增3個預設變數:ACTION、DEVPATH和SUBSYSTEM。這裡的ACTION是add,DEVPATH是/devices/platform/demo_device/,SUBSYSTEM是所在的匯流排platform。

	if (uevent_ops && uevent_ops->uevent) {
		retval = uevent_ops->uevent(kset, kobj, env);
		if (retval) {
			pr_debug("kobject: '%s' (%p): %s: uevent() returned "
				 "%d\n", kobject_name(kobj), kobj,
				 __func__, retval);
			goto exit;
		}
	}

這裡就讓kset的uevent_ops->uevent函式繼續新增它所需要的uevent變數,這裡就呼叫前面的dev_uevent函式,來看一下:

	/* add device node properties if present */
	if (MAJOR(dev->devt)) {
		const char *tmp;
		const char *name;
		umode_t mode = 0;
		kuid_t uid = GLOBAL_ROOT_UID;
		kgid_t gid = GLOBAL_ROOT_GID;

		add_uevent_var(env, "MAJOR=%u", MAJOR(dev->devt));
		add_uevent_var(env, "MINOR=%u", MINOR(dev->devt));
		name = device_get_devnode(dev, &mode, &uid, &gid, &tmp);
		if (name) {
			add_uevent_var(env, "DEVNAME=%s", name);
			if (mode)
				add_uevent_var(env, "DEVMODE=%#o", mode & 0777);
			if (!uid_eq(uid, GLOBAL_ROOT_UID))
				add_uevent_var(env, "DEVUID=%u", from_kuid(&init_user_ns, uid));
			if (!gid_eq(gid, GLOBAL_ROOT_GID))
				add_uevent_var(env, "DEVGID=%u", from_kgid(&init_user_ns, gid));
			kfree(tmp);
		}
	}

......

	if (dev->driver)
		add_uevent_var(env, "DRIVER=%s", dev->driver->name);

......

	/* have the bus specific function add its stuff */
	if (dev->bus && dev->bus->uevent) {
		retval = dev->bus->uevent(dev, env);
		if (retval)
			pr_debug("device: '%s': %s: bus uevent() returned %d\n",
				 dev_name(dev), __func__, retval);
	}

	/* have the class specific function add its stuff */
	if (dev->class && dev->class->dev_uevent) {
		retval = dev->class->dev_uevent(dev, env);
		if (retval)
			pr_debug("device: '%s': %s: class uevent() "
				 "returned %d\n", dev_name(dev),
				 __func__, retval);
	}

	/* have the device type specific function add its stuff */
	if (dev->type && dev->type->uevent) {
		retval = dev->type->uevent(dev, env);
		if (retval)
			pr_debug("device: '%s': %s: dev_type uevent() "
				 "returned %d\n", dev_name(dev),
				 __func__, retval);
	}
這裡的if分支現在不會執行到,在裝置擁有主裝置號後會新增MAJOR、MINOR、DEVNAME等uevent變數,然後如果已經綁定了裝置驅動則會新增DRIVER變數,最後如果裝置有指定匯流排bus_type和class,則會調它們各自的uevent函式來繼續追加uevent變數,這裡已經綁定了bus_type為platform_bus_type,則呼叫的uevent函式就是platform_uevent:
static int platform_uevent(struct device *dev, struct kobj_uevent_env *env)
{
	struct platform_device	*pdev = to_platform_device(dev);
	int rc;

	/* Some devices have extra OF data and an OF-style MODALIAS */
	rc = of_device_uevent_modalias(dev, env);
	if (rc != -ENODEV)
		return rc;

	add_uevent_var(env, "MODALIAS=%s%s", PLATFORM_MODULE_PREFIX,
			pdev->name);
	return 0;
}
該函式向uevent訊息中追加變數MODALIAS,那這裡就是“MODALIAS=platform:demo_device”,這條先記住,後面會用到。
#if defined(CONFIG_NET)
......
		skb = alloc_skb(len + env->buflen, GFP_KERNEL);
......
			retval = netlink_broadcast_filtered(uevent_sock, skb,
							    0, 1, GFP_KERNEL,
							    kobj_bcast_filter,
							    kobj);

接著回到kobject_uevent_env()函式中,如果已經配置了NET條件編譯選項,則核心使用netlink機制實現向用戶空間的uevent訊息傳送,在本文最開始部分已經使能了這一配置,所以uevent就在這裡發出了,內部的細節就不詳細分析了。

到這裡為止,demo_device裝置就註冊完成了。

2、udev接收設備註冊的uevent訊息並載入驅動

前面講到demo_device設備註冊完成後會向用戶空間傳送uevent訊息。而在使用者空間就由udev來接收和處理。udev工具非常強大,它能夠載入驅動模組,規定如何給裝置節點命名、建立符號連結,裝置載入或刪除時執行指定的程式等。udev工具的核心是服務程序udevd,當udevd程序啟動後,它將預設從/lib/udev/rules.d目錄下載入和分析規則檔案(可以在編譯時指定目錄,使用者自行定義的規則在/etc/udev/rules.d目錄下),然後udevd程序監聽來自核心空間的uevent訊息,它會對uevent訊息內部的每個變數進行識別和匹配,並按照規則檔案做出相應的動作。

此時需要分兩種情況來討論:

(1)情況一:udevd服務程序已經啟動並接收到了核心發出的uevent訊息。

這種情況下,udevd程序會按照規則檔案來處理uevent訊息,在前文中搭建環境時我往rules.d拷貝了規則檔案80-drivers.rules,udevd就會按照它來載入驅動先來看一下80-drivers.rules:
ACTION=="remove", GOTO="drivers_end"

DRIVER!="?*", ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe -bv $env{MODALIAS}"
......

LABEL="drivers_end"
這裡如果uevent的ACTION型別為remove則直接退出,什麼都不執行,如果uevent中的DRIVER環境變數為空且MODALIAS不為空,則執行以下命令:
/sbin/modprobe –bv $env{MODALIAS}
在前文載入demo_device時,已經看到他的uevent訊息包含了MODALIAS變數,內容就是“platform:demo_device”,那麼這裡執行的命令就轉換為如下形式:
/sbin/modprobe –bvplatform:demo_device
但是這platform:demo_device又是什麼東西,其實它就是驅動別名,來看demo_driver.c中的最後一行:
 MODULE_ALIAS("platform:demo_device");
通過MODULE_ALIAS指定了驅動的別名(對於那種支援多種裝置的驅動程式則一般包含一個device_table,通過MODULE_DEVICE_TABLE巨集來指定一組驅動別名,見後面參考文獻)。這其實就可以理解為一種將驅動別名和驅動模組的“繫結”。
前文中已經看到在/lib/modules/3.12.35/modules.alia檔案中包含了:
alias platform:demo_device demo_driver
這裡呼叫modprobe載入 platform:demo_device其實就是載入demo_driver.ko。
至此,在udevd服務程序已經啟動的情況下,裝置驅動程式被正確的載入了。
(2)情況二:udevd服務程序沒有啟動,錯過了該訊息。
有如下一種場景:核心啟動時會列舉儲存裝置,然後掛載initramfs,然而此時的udevd程序尚未啟動,也就無法接受uevent訊息而載入儲存裝置的驅動,最終導致啟動失敗。針對這一情況,除了事先指定以外又該如何載入合適的驅動程式呢。
其實此時udevadm工具就派上用場了,前文的示例中,在中啟動udevd服務程序後手動執行如下命令來試核心再次向用戶空間傳送KOBJ_ADD型別的uevent訊息:
udevadm trigger--action=add
這條命令其實會遍歷sysfs檔案系統devices下的uevent檔案(/sys/devices/***/uevent),然後寫入字串“add”(手動執行echoadd > /sys/devices/***/uevent也可以達到同樣的效果)。

這裡會向/sys/devices/platform/demo_device/uevent檔案寫入“add”,前文中已經看到這個檔案是在註冊demo_devices是呼叫platform_device_register()->platform_device_add()->device_add()->device_create_file()->sysfs_create_file建立的。另外已經繫結demo_device ->device->kobject->ktype 為device_ktype:

static struct kobj_type device_ktype = {
	.release	= device_release,
	.sysfs_ops	= &dev_sysfs_ops,
	.namespace	= device_namespace,
};
在向/sys/devices/platform/demo_device/uevent檔案寫入“add”時會首先open這個檔案,經過系統呼叫後將呼叫到sysfs檔案系統的open介面sysfs_open_file():
	/* every kobject with an attribute needs a ktype assigned */
	if (kobj->ktype && kobj->ktype->sysfs_ops)
		ops = kobj->ktype->sysfs_ops;

	buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL);
	if (!buffer)
		goto err_out;

	buffer->ops = ops;
	file->private_data = buffer;
sysfs_open_file()函式首先找到kobject->ktype(這裡即device_ktype),找到sysfs_ops(這裡即dev_sysfs_ops);然後為sysfs_buffer指標buffer分配空間並將剛才找到的sysfs_ops儲存到buffer->ops中,最後將整個buffer儲存到file結構體中(核心會為每一個開啟的檔案動態生成一個檔案描述符fd和file結構體),以後對這個檔案的read、write系統呼叫就會呼叫file->private_data->ops中的store函式指標,這裡就是dev_attr_store(),來看一下這個函式:
static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,
			      const char *buf, size_t count)
{
	struct device_attribute *dev_attr = to_dev_attr(attr);
	struct device *dev = kobj_to_dev(kobj);
	ssize_t ret = -EIO;

	if (dev_attr->store)
		ret = dev_attr->store(dev, dev_attr, buf, count);
	return ret;
}

這裡找到attr屬性檔案所屬的device_attribute中指定的store介面,前文中建立uevent屬性檔案是就已定義為uuevent_store:

static ssize_t uevent_store(struct device *dev, struct device_attribute *attr,
			    const char *buf, size_t count)
{
	enum kobject_action action;

	if (kobject_action_type(buf, count, &action) == 0)
		kobject_uevent(&dev->kobj, action);
	else
		dev_err(dev, "uevent: unknown action-string\n");
	return count;
}
這裡通過kobject_action_type對輸入的“add”進行驗證與匹配,最終匹配到的action為KOBJ_ADD,於是呼叫kobject_uevent(&dev->kobj, KOBJ_ADD);傳送KOBJ_ADD型別的uevent事件。後續的工作就同“情況一”一樣了。
到這裡為止,demo_device裝置的驅動模組demo_driver.ko就被正確的識別並載入了。
 
3、驅動匹配並建立裝置節點。
在驅動模組demo_driver.ko載入後,驅動模組執行了哪些工作來確保載入的驅動模組就能和demo_device裝置匹配,如果能夠匹配又該如何建立裝置節點,向用戶空間提供操作驅動的介面。下面來分析demo_driver.c:
在驅動模組的init函式中呼叫platform_driver_register函式(其實是一個巨集)載入驅動,來看一下這個函式:
int __platform_driver_register(struct platform_driver *drv,
				struct module *owner)
{
	drv->driver.owner = owner;
	drv->driver.bus = &platform_bus_type;
	if (drv->probe)
		drv->driver.probe = platform_drv_probe;
	if (drv->remove)
		drv->driver.remove = platform_drv_remove;
	if (drv->shutdown)
		drv->driver.shutdown = platform_drv_shutdown;

	return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__platform_driver_register);
該函式首先設定driver->bus_type為platform_bus_type,然後記下driver結構中的幾個函式指標,其中probe函式將在platform裝置和platform驅動匹配後被呼叫。然後呼叫driver_register繼續完成註冊:
	other = driver_find(drv->name, drv->bus);
	if (other) {
		printk(KERN_ERR "Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EBUSY;
	}

	ret = bus_add_driver(drv);
	if (ret)
		return ret;

這個函式中首先查詢總線上是否有相同名字的driver以防止重複註冊驅動,然後主要關注bus_add_driver,它完成了核心部分的註冊和與裝置的匹配工作:

	if (drv->bus->p->drivers_autoprobe) {
		error = driver_attach(drv);
		if (error)
			goto out_unregister;
	}

如果drivers_autoprobe置位則會呼叫driver_attach進行匹配工作,這裡platform_bus_type在初始化時已經置位了,進入driver_attach():

int driver_attach(struct device_driver *drv)
{
	return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
這將輪詢總線上掛載的裝置然後呼叫__driver_attach()函式:
static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)
{
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
__driver_attach()函式函式首先呼叫driver_match_devices()進行匹配工作:
static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

這裡會嘗試使用多種匹配方式,我這裡使用最“古老”的方式,那就是裝置和驅動的名字,由於demo_device和demo_driver的名字都是“demo_device”,所以匹配成功。和裝置匹配成功後所要做的第一件事情就是呼叫驅動初始化函式對裝置進行初始化工作。

	if (!dev->driver)
		driver_probe_device(drv, dev);

回到__driver_attach()函式,接著由於裝置尚未繫結驅動,所以呼叫driver_probe_device->really_probe()來繫結裝置並呼叫驅動的probe函式,完成驅動的初始化:

	dev->driver = drv;
......
	if (dev->bus->probe) {
		ret = dev->bus->probe(dev);
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {
		ret = drv->probe(dev);
		if (ret)
			goto probe_failed;
	}

really_probe()首先將driver和devices繫結,然後就可以看到這裡呼叫了driver->probe函式,於是demo_driver.c中的demo_driver_probe()函式被呼叫了。函式返回後,bus_add_driver()函式進行剩餘的初始化工作,platform_driver_register就將demo_driver註冊成功了,同時也對裝置進行了繫結。

下面回到demo_driver.c的init函式中,這裡除了註冊驅動以外,其實還做了一件事,那就是建立了一個class,這是在後面為probe函式中呼叫device_create建立裝置節點做準備工作。class是一種對devices更高層次的抽象,用來作為同類功能裝置的一個容器(由此可見Linux裝置驅動模型已經將C語言實現面向物件的思想發揮的淋漓盡致)。這裡並不是重點,來簡單看一下就可以了:

#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})
這裡呼叫class來生成類物件demo_class,

	cls = kzalloc(sizeof(*cls), GFP_KERNEL);
	if (!cls) {
		retval = -ENOMEM;
		goto error;
	}

	cls->name = name;
	cls->owner = owner;
	cls->class_release = class_create_release;

	retval = __class_register(cls, key);
	if (retval)
		goto error;

首先為class物件分配空間,然後為name、owner等欄位賦值,接著呼叫__class_register開始向核心註冊:

	cp = kzalloc(sizeof(*cp), GFP_KERNEL);
	if (!cp)
		return -ENOMEM;
	......
	error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
首先為私有資料物件subsys_private分配空間,然後設定kobj的name欄位為class的nema,這裡就是“demo_dev”。
	cp->subsys.kobj.kset = class_kset;
        ......
	cp->subsys.kobj.ktype = &class_ktype;
	cp->class = cls;
	cls->p = cp;

然後為kobj指定kset為class_kset(在核心初始化時呼叫class_init建立),同時指定ktype為class_ktype。

	error = kset_register(&cp->subsys);
	if (error) {
		kfree(cp);
		return error;
	}
這裡註冊kset,至此將在/sys/class目錄下生成新建立的demo_dev目錄。
bash-4.3# ls -l/sys/class/
drwxr-xr-x 2 0 0 0 Jan 1 02:14 demo_dev

回到demo_driver.c程式中,前面在註冊驅動程式的過程中,已經呼叫了驅動的probe函式demo_driver_probe(),在該函式中建立了一個字元裝置,它的主裝置號由核心分配,然後呼叫device_create()函式,該函式將通過udev或devtmpfs動態建立裝置節點,來看一下這個函式:

struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)
{
	va_list vargs;
	struct device *dev;

	va_start(vargs, fmt);
	dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
	va_end(vargs);
	return dev;
}

該函式入參class指標指定為剛才在init函式中建立的demo_class,parent指標和drvdata指標為空,devt為字元裝置的主裝置號,最後fmt為字元裝置名“demo_dev”。繼續進入device_create_vargs()->device_create_groups_vargs():

	struct device *dev = NULL;
	int retval = -ENODEV;

	if (class == NULL || IS_ERR(class))
		goto error;

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev) {
		retval = -ENOMEM;
		goto error;
	}

	dev->devt = devt;
	dev->class = class;
	dev->parent = parent;
	dev->groups = groups;
	dev->release = device_create_release;
	dev_set_drvdata(dev, drvdata);
這裡首先為新建立的devices結構分配記憶體,然後初始化變數,包括設定主裝置號、class、parent等等。
	retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
	if (retval)
		goto error;

	retval = device_register(dev);
	if (retval)
		goto error;

	return dev;

這裡首先為新分配的device->kobj新增名字,然後呼叫device_register註冊該新生成的device結構:

int device_register(struct device *dev)
{
	device_initialize(dev);
	return device_add(dev);
}
EXPORT_SYMBOL_GPL(device_register);
這裡的兩個函式前面已經看到過了,其中device_initialize就不再分析了,但是device還要再進入看一下,這裡有3點需要注意:(1)device沒有掛載到任何的總線上,所以他的bus指標為空,(2)device的父裝置沒有指定,所以他的parent指標也為空。(3)device指定了class,因此這裡將會走另一個分支。
	parent = get_device(dev->parent);
	kobj = get_device_parent(dev, parent);
	if (kobj)
		dev->kobj.parent = kobj;

這裡的parent指標為空,而kobj的父指標會在get_device_parent()中指定,而他裡面會根據parent、bus和class的有無來走不同的分支,在前文中註冊demo_device時由於已經執行了parent所以直接返回parent的kobj,但是這裡只有class非空,這裡會走入以下分支:

	if (dev->class) {
		struct kobject *kobj = NULL;
		struct kobject *parent_kobj;
		struct kobject *k;
......
		if (parent == NULL)
			parent_kobj = virtual_device_parent(dev);
......
		/* find our class-directory at the parent and reference it */
		spin_lock(&dev->class->p->glue_dirs.list_lock);
		list_for_each_entry(k, &dev->class->p->glue_dirs.list, entry)
			if (k->parent == parent_kobj) {
				kobj = kobject_get(k);
				break;
			}
		spin_unlock(&dev->class->p->glue_dirs.list_lock);
		if (kobj) {
			mutex_unlock(&gdp_mutex);
			return kobj;
		}

		/* or create a new class-directory at the parent device */
		k = class_dir_create_and_add(dev->class, parent_kobj);
		/* do not emit an uevent for this simple "glue" directory */
		mutex_unlock(&gdp_mutex);
		return k;

這裡會為parent_kobj創建出一個名為“virtual”kobject(如果已經有了則直接返回賦值),它的父指標為devices_kset->kobj。然後開始遍歷class->p->glue_dirs.list查詢是否已經add了父裝置為“virtual”的kobject,由於class是新建的,因此這裡當然不會找到,這裡的呼叫class_dir_create_and_add建立之,就會在/sys/devices/virtual/目錄下建立demo_dev目錄。

	error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
	if (error)
		goto Error;

回到device_add函式,在選擇好了父kobject後,這裡會在/sys/devices/virtual/demo_dev/建立demo_dev目錄。

	error = device_create_file(dev, &dev_attr_uevent);
	if (error)
		goto attrError;
這裡在/sys/devices/virtual/demo_dev/demo_dev/目錄下建立了uevent屬性檔案,同樣繫結讀寫函式為uevent_show和uevent_store。如果cat這個uevent檔案會打印出uevent訊息的各個變數:
bash-4.3# cat uevent
MAJOR=248
MINOR=0
DEVNAME=demo_dev
可見,我環境中核心自動分配的主裝置號位248。
	if (MAJOR(dev->devt)) {
		error = device_create_file(dev, &dev_attr_dev);
		if (error)
			goto ueventattrError;

		error = device_create_sys_dev_entry(dev);
		if (error)
			goto devtattrError;

		devtmpfs_create_node(dev);
	}
繼續device_add()往下分析,這時由於已經分配了主裝置號,所以這個分支就可以去了,首先會在/sys/devices/virtual/demo_dev/demo_dev目錄下建立dev屬性檔案,並且繫結讀函式為dev_show(只讀屬性檔案,沒有繫結寫函式):
bash-4.3# ls -ldev

-r--r--r-- 1 0 0 4096 Jan  1 03:32 dev

static ssize_t dev_show(struct device *dev, struct device_attribute *attr,
			char *buf)
{
	return print_dev_t(buf, dev->devt);
}
這個函式就是打印出裝置的主次裝置號:
bash-4.3# cat dev
248:0

接著如果核心已經支援了devtmpfs就會穿件裝置節點,但是後面可以看到udevd也會建立裝置節點,其實devtmpfs和udev並不衝突,udev可以在devtmpfs上進行使用者空間的各種修飾。

	kobject_uevent(&dev->kobj, KOBJ_ADD);
繼續device_add()往下分析,又看到了kobject_uevent函式,這次它向用戶空間傳送的uevent訊息就和前面的“demo_devices”有所不同了。
	/* search the kset we belong to */
	top_kobj = kobj;
	while (!top_kobj->kset && top_kobj->parent)
		top_kobj = top_kobj->parent;
......
	kset = top_kobj->kset;
	uevent_ops = kset->uevent_ops;

這裡同樣是找到kobj所屬的kset和其中的uevent_ops,此時的kset也是devices_kset,參照前文的流程分析,這裡呼叫dev_uevent後會進入下面的分支:

	/* add device node properties if present */
	if (MAJOR(dev->devt)) {
		const char *tmp;
		const char *name;
		umode_t mode = 0;
		kuid_t uid = GLOBAL_ROOT_UID;
		kgid_t gid = GLOBAL_ROOT_GID;

		add_uevent_var(env, "MAJOR=%u", MAJOR(dev->devt));
		add_uevent_var(env, "MINOR=%u", MINOR(dev->devt));
		name = device_get_devnode(dev, &mode, &uid, &gid, &tmp);
		if (name) {
			add_uevent_var(env, "DEVNAME=%s", name);
			if (mode)
				add_uevent_var(env, "DEVMODE=%#o", mode & 0777);
			if (!uid_eq(uid, GLOBAL_ROOT_UID))
				add_uevent_var(env, "DEVUID=%u", from_kuid(&init_user_ns, uid));
			if (!gid_eq(gid, GLOBAL_ROOT_GID))
				add_uevent_var(env, "DEVGID=%u", from_kgid(&init_user_ns, gid));
			kfree(tmp);
		}
	}

為uevent訊息新增MAJOR和MINOR變數(前面都uevent檔案時已經看到了),結束了之後依然是通過netlink向用戶空間udevd服務程式傳送socket訊息。udev在解析了之後就發現uevent變數中包含了裝置號,則通過系統呼叫mknod建立裝置節點。至此裝置檔案就可以正常操作,使用者可以通過它和裝置驅動進行互動了。

參考文獻:
1.《深入Linux裝置驅動核心機制》

2.《深度探索Linux作業系統——系統構建和原理解析》


相關推薦

Linux裝置驅動模組載入示例原理解析

本文介紹Linux裝置驅動模組在設備註冊時如何實現自動載入和建立裝置節點。 在Linux系統中,基於sysfs檔案系統、裝置驅動模型和udev工具可以實現在裝置模組“冷、熱”載入時自動載入裝置對應的驅動程式,同時可以按需在/dev目錄下建立裝置節點。 本文中我搭建好環

學習Linux-4.12核心網路協議棧(1.8)——網路裝置驅動模組載入

1.瞭解PCI匯流排 說到網路裝置驅動,就不得不說PCI匯流排,但是這個話題可深可淺,而且網上的資料也是一大堆(比如),但是對於我們來說,目前並不需要掌握很深,下面是網上找的兩張最基本的PCI工作結構圖,雖然PCI總線上可以掛接不同種類的裝置,但我們這裡只要瞭解網路裝置就夠

linux裝置驅動載入的先後順序

                        &

linux裝置驅動原理本質

任何計算機系統都是軟體和硬體的結合體,如果只有硬體而沒有軟體,則硬體是沒有靈魂的軀殼;如果只有軟體沒有硬體,則軟體就是一堆無用的字元。在底層硬體的基礎上,作業系統覆蓋一層驅動,遮蔽底層硬體的操作,通過特定的軟體介面去操作底層硬體,使用者在使用者空間可以很容易的把軟體設計目標放在策略與需求上,可以很方

淺談裝置驅動的作用本質,有無作業系統Linux裝置驅動的區別

  一、驅動的作用 任何一個計算機系統的執行都是系統中軟硬體協作的結果,沒有硬體的軟體是空中樓閣,而沒有軟體的硬體則只是一堆廢鐵。硬體是底層基礎,是所有軟體得以執行的平臺,程式碼最終會落實為硬體上的組合邏輯與時序邏輯;軟體則實現了具體應用,它按照各種不同的業務需求而設計,滿足了使用

IO記憶體 IO埠 >>Linux 裝置驅動程式

啥時候要是寫程式碼的時候像玩遊戲一樣開心就好了,我覺得那一天應該不會遙遠,要做一個快樂的小二逼 哈哈哈; 懂得越多責任就越重大,喜歡責任重大,那就要讓自己一天天的變強大。 如是說就得每天早上給自己一杯自己造的“雞血”喝,熱乎乎的比別人給的容易喝下去;不是嗎? 文章

管理訊號量、旋鎖、原子變數函式介面>>Linux 裝置驅動程式

文章目錄 [0x100] 程序競態特徵 [0x200] 訊號量 [0x210] 程序訊號量函式介面[struct semaphore] [0x211] 初始化訊號量 [0x212] 獲取與釋放訊號量 [0x2

Linux裝置驅動之button按鍵驅動學習小結

button按鍵驅動,相對於前面的LED驅動來說。增加了中斷處理以及阻塞與非阻塞方式等新知識點。 先上學習的驅動程式碼。 核心:linux3.0 板子:fl2440 /***************************************************

Linux裝置驅動--LCD平臺裝置驅動(tiny4412)

1 環境與簡介     Host:Ubuntu14.04(64bit)     Target:Tiny4412     Kernel:linux-3.5.0 2 平臺裝置 2.1 宣告 extern struct platform_device s5p_device_fim

linux裝置驅動程式中的阻塞、IO多路複用非同步通知機制

一、阻塞與非阻塞 阻塞與非阻塞是裝置訪問的兩種方式。在寫阻塞與非阻塞的驅動程式時,經常用到等待佇列。 阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起,函式只有在得到結果之後才會返回。 非阻塞指不能立刻得到結果之前,該函式不會阻塞當前程序,而會立刻返回。 函式是否處於阻

宋寶華《Linux裝置驅動開發詳解》——sysfs檔案系統linux裝置模型(5.4.2)

以下讀書筆記內容,摘自宋寶華《Linux裝置驅動開發詳解》一書。 1、sysfs檔案系統的簡介 (1)linux2.6以後的核心引進syfs檔案系統,是虛擬檔案系統; (2)產生一個包括所有系統硬體

Linux裝置驅動中的阻塞非阻塞I/O

阻塞和非阻塞I/O是裝置訪問的兩種不同模式,驅動程式可以靈活的支援使用者空間對裝置的這兩種訪問方式 本例子講述了這兩者的區別 並實現I/O的等待佇列機制, 並進行了使用者空間的驗證 基本概念: 1> 阻塞操作      是指 在執行裝置操作時,若不能獲得資源,則掛起程

linux裝置驅動第二篇:構造和執行模組

上一篇介紹了linux驅動的概念,以及linux下裝置驅動的基本分類情況及其各個分類的依據和差異,這一篇我們來描述如何寫一個類似hello world的簡單測試驅動程式。而這個驅動的唯一功能就是輸出hello world。 在編寫具體的例項之前,我們先來了解下linux核心

linux裝置驅動-旋鎖和中斷遮蔽

自旋鎖 自旋鎖:為防止多處理器併發引入的一種鎖 在核心中,廣泛應用於中斷處理部分 4中自旋鎖: 自旋鎖 讀寫自旋鎖 順序鎖 順序鎖(seqlock)是對讀寫鎖的一種優化,若使用順序鎖,讀執行單元絕不會被寫執行單元阻塞,也就是說,讀執行單元可以

88w8686 wifi模組linux裝置驅動的測試

執行指令碼檔案wifisetup.sh來掃描並連線wifi熱點“cctest”,併為無線網絡卡分配IP地址192.168.43.25。如下所示。(注意,應事先把wifi_driver\FwImage目錄下的韌體程式helper_gspi.bin和gspi8686.bin放在

linux裝置驅動之阻塞非阻塞I/O

先做一下與核心阻塞有關的知識儲備: 1)程序休眠:     程序休眠,簡單的說就是正在執行的程序讓出CPU。休眠的程序會被核心擱置在在一邊,只有當核心再次把休眠的程序喚醒,程序才會會重新在CPU執行。這是核心中的程序排程。一個CPU在同一時間只能有一個程序在執行,微觀序列巨

Linux裝置驅動--LCD平臺裝置驅動(smdk2440)

1 環境與簡介     Host:Ubuntu14.04(64bit)     Target:smdk2440     Kernel:linux-2.6.39.4     類似於《Linux裝置驅動--WDT平臺裝置與驅動》,本文再以LCD為例進行說明。本文的原始碼均來自L

Linux裝置驅動--LCD平臺裝置驅動(s3c64xx)

1 開發環境     Host:Ubuntu14.04     Target:s3c64xx     Kernel:linux-3.18.2 2 平臺裝置         關於裝置樹是如果被載入並解析成裝置節點的,詳見參考資料[1],本文重點分析如何利用裝置節點建立相

linux裝置驅動第五篇:驅動中的併發竟態

綜述 在上一篇介紹了linux驅動的除錯方法,這一篇介紹一下在驅動程式設計中會遇到的併發和竟態以及如何處理併發和競爭。 首先什麼是併發與竟態呢?併發(concurrency)指的是多個執行單元同時、並行被執行。而併發的執行單元對共享資源(硬體資源和軟體上的全域性、靜態變數)

Linux裝置驅動--LCD平臺裝置驅動(smdk6410)

1 環境與簡介     Host:Ubuntu14.04(64bit)     Target:smdk6410     Kernel:linux-3.5.0     在《Linux裝置驅動--LCD平臺裝置與驅動(smdk2440)》中基於linux-2.6.39.4對LC