首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux 字符設備驅動:內核態與用戶態數據雙向傳輸完整實例

     編輯:關於C語言

Linux 字符設備驅動:內核態與用戶態數據雙向傳輸完整實例

本文提供一個完整的字符設備驅動開發範例,涵蓋動態設備號分配、設備節點自動創建、ioctl 命令定義(_IO/_IOR/_IOW/_IOWR)、copy_from_user/copy_to_user 數據傳輸、用戶態測試程序、Makefile 交叉編譯全流程。

1. ioctl 命令定義(my_cdev.h)

#ifndef __MY_CDEV_H__
#define __MY_CDEV_H__

#define LED_MAGIC 'L'
#define LED_ON  _IOW(LED_MAGIC, 0, int)    // 用戶→內核:寫入參數
#define LED_OFF _IOR(LED_MAGIC, 1, int *)  // 內核→用戶:讀取結果

#endif

_IOW 表示寫(用戶到內核),_IOR 表示讀(內核到用戶),宏自動編碼方向、幻數、序號、參數大小,避免 2.4 內核時代的號碼衝突。

2. 內核模塊核心代碼(my_char_dev.c)

#include 
#include 
#include 
#include 
#include 
#include 
#include "my_cdev.h"

struct cdev cdev;
dev_t devno;
struct class *cdev_class;
int param;  // 內核側暫存變量

int my_cdev_open(struct inode *node, struct file *filp) {
    printk("my_cdev_open success\n");
    return 0;
}

long my_cdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    int rc = -1;
    switch(cmd) {
        case LED_ON:
            rc = copy_from_user(¶m, (int __user*)arg, sizeof(int));
            if (rc) { printk("copy_from_user failed\n"); break; }
            printk("Param = %d, LED_ON set\n", param);
            return 0;
        case LED_OFF:
            printk("LED_OFF set\n");
            param = 1000;
            rc = copy_to_user((int __user*)arg, ¶m, sizeof(int));
            if (rc) printk("copy_to_user failed\n");
            param = 0;
            return 0;
        default: return -EINVAL;
    }
    return rc;
}

struct file_operations my_cdev_fops = {
    .open = my_cdev_open,
    .unlocked_ioctl = my_cdev_ioctl,
};

static int __init my_cdev_init(void) {
    int ret = alloc_chrdev_region(&devno, 0, 1, "my_chardev");
    if (ret) return ret;
    cdev_init(&cdev, &my_cdev_fops);
    ret = cdev_add(&cdev, devno, 1);
    if (ret) { unregister_chrdev_region(devno, 1); return ret; }
    cdev_class = class_create(THIS_MODULE, "my_chardev");
    if (IS_ERR(cdev_class)) { unregister_chrdev_region(devno, 1); return -1; }
    device_create(cdev_class, NULL, devno, NULL, "my_chardev"); // /dev/my_chardev
    return 0;
}

static void __exit my_cdev_exit(void) {
    device_destroy(cdev_class, devno);
    class_destroy(cdev_class);
    cdev_del(&cdev);
    unregister_chrdev_region(devno, 1);
    printk("my_cdev_exit\n");
}

module_init(my_cdev_init);
module_exit(my_cdev_exit);
MODULE_LICENSE("GPL");

3. 用戶態測試程序(my_char_dev_app.c)

#include 
#include 
#include 
#include 
#include "my_cdev.h"

int main(int argc, char *argv[]) {
    if (argc < 2) { printf("Usage: %s <1|2>\n", argv[0]); return 0; }
    int fd = open("/dev/my_chardev", O_RDWR);
    if (fd < 0) { perror("open"); return 0; }
    unsigned long param = 0;
    int cmd = atoi(argv[1]);
    switch(cmd) {
        case 1: param = 500; ioctl(fd, LED_ON, ¶m); break;
        case 2: ioctl(fd, LED_OFF, ¶m); printf("read: %lu\n", param); break;
    }
    close(fd);
    return 0;
}

4. 交叉編譯 Makefile

obj-m := my_char_dev.o
KDIR := /home/win/dn377org/trunk/bcm7252/linux/
all:
    make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
clean:
    rm -f *.ko *.o *.mod.* *.symvers *.order

5. 測試步驟與輸出

# 載入模塊
insmod my_char_dev.ko
# 自動創建 /dev/my_chardev

# 測試寫入 (cmd=1)
./my_char_dev_app 1
# 內核日誌: Param = 500, LED_ON set

# 測試讀取 (cmd=2)
./my_char_dev_app 2
# 用戶態輸出: read: 1000
# 內核日誌: LED_OFF set

insmod app run

關鍵點總結

  • 動態分配設備號(alloc_chrdev_region)+ class_create/device_create 自動生成設備節點,無需手動 mknod。
  • copy_from_user/to_user 必須在進程上下文(如 ioctl/open/read/write)中調用,不可在中斷上下文使用。
  • ioctl 命令編碼務必使用 _IO* 宏,保證跨架構兼容。
  • 模塊卸載時按相反順序釋放資源:device_destroy → class_destroy → cdev_del → unregister_chrdev_region。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved