title: LED与蜂鸣器驱动 tags: [Linux驱动, LED, 蜂鸣器, 嵌入式, GPIO, 设备树, 字符设备] created: 2026-09-16 updated: 2026-09-17 pdf_ref:
关联知识: [[03-pinctrl与gpio子系统]] | [[02-Linux字符设备驱动框架]] | [[04-设备树基础]]
I.MX6U-ALPHA开发板上的LED灯连接到I.MX6ULL的GPIO1_IO03引脚,为低电平有效(GPIO输出低电平时LED点亮)。硬件电路核心要素:
| 项目 | 说明 |
|---|---|
| 控制引脚 | GPIO1_IO03 |
| 有效电平 | 低电平有效(Active Low) |
| 驱动方式 | GPIO推挽输出 |
| 限流电阻 | 串联在LED回路中 |
Linux下的任何外设驱动,最终都是要配置相应的硬件寄存器。LED驱动的本质就是对I.MX6ULL的GPIO口进行配置。涉及的关键寄存器:
| 寄存器 | 物理地址 | 功能 |
|---|---|---|
| CCM_CCGR1 | 0X020C406C | 时钟使能寄存器 |
| IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 | 0X020E0068 | 复用功能配置 |
| IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 | 0X020E02F4 | IO属性配置 |
| GPIO1_DR | 0X0209C000 | 数据寄存器 |
| GPIO1_GDIR | 0X0209C004 | 方向寄存器 |
Linux开启MMU后,CPU访问的都是虚拟地址。物理寄存器地址不能直接操作,必须通过地址映射:
映射后的读写操作推荐使用内核提供的I/O内存访问函数:
// 读操作
u8 readb(addr); // 8bit读
u16 readw(addr); // 16bit读
u32 readl(addr); // 32bit读
// 写操作
writeb(value, addr); // 8bit写
writew(value, addr); // 16bit写
writel(value, addr); // 32bit写
Linux LED驱动采用字符设备驱动框架,核心结构:
用户空间 APP
↓ open/read/write
/dev/led 设备节点
↓ file_operations
LED驱动(led_fops)
↓ led_switch()
硬件寄存器(GPIO1_IO03)
驱动加载流程:ioremap地址映射 → 使能时钟 → 配置复用 → 设置方向 → 注册字符设备 → 创建设备节点
📖 章节说明:第四十一章的 LED 驱动采用直接操作寄存器的方式,未使用设备树;本节设备树版本对应《指南》第四十五章《设备树下的 LED 驱动实验》及后续 pinctrl/gpio 子系统章节,属相邻章节补充内容。
设备树(Device Tree)用于描述硬件信息,避免在驱动中硬编码硬件参数。LED设备树配置包含两部分:pinctrl节点和设备节点。
pinctrl节点定义引脚的复用功能和电气属性。在imx6ull-alientek-emmc.dts的iomuxc节点下添加:
pinctrl_led: ledgrp {
fsl,pins = <
MX6ULL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED */
>;
};
| 属性 | 说明 |
|---|---|
fsl,pins |
飞思卡尔平台引脚配置属性 |
MX6ULL_PAD_GPIO1_IO03__GPIO1_IO03 |
宏定义,将引脚复用为GPIO |
0x10B0 |
IO属性值(驱动强度、上下拉等) |
宏MX6ULL_PAD_GPIO1_IO03__GPIO1_IO03定义在arch/arm/boot/dts/imx6ull-pinfunc.h中。
设备节点通过GPIO属性引用pinctrl配置的引脚:
led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
| 属性 | 说明 |
|---|---|
&gpio1 |
引用gpio1控制器节点 |
3 |
GPIO编号(GPIO1_IO03的第3个引脚) |
GPIO_ACTIVE_LOW |
低电平有效 |
/* 在根节点"/"下添加 */
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "alientek,gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
| 属性 | 说明 |
|---|---|
compatible |
匹配驱动的字符串,驱动中of_match_table使用 |
pinctrl-names |
pinctrl状态名称 |
pinctrl-0 |
引用默认状态的pinctrl配置 |
led-gpios |
自定义属性名,驱动中通过of_get_named_gpio获取 |
status |
"okay"表示启用该节点 |
验证设备树是否修改成功:编译设备树(make dtbs)后启动系统,检查/proc/device-tree/gpioled节点是否存在。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LED_MAJOR 200
#define LED_NAME "led"
#define LEDOFF 0
#define LEDON 1
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
}else if(sta == LEDOFF) {
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
}
}
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
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");
return -EFAULT;
}
ledstat = databuf[0];
if(ledstat == LEDON) {
led_switch(LEDON);
} else if(ledstat == LEDOFF) {
led_switch(LEDOFF);
}
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
static int __init led_init(void)
{
int retvalue = 0;
u32 val = 0;
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
/* 2、使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26);
val |= (3 << 26);
writel(val, IMX6U_CCM_CCGR1);
/* 3、设置GPIO1_IO03复用为GPIO,设置IO属性 */
writel(5, SW_MUX_GPIO1_IO03);
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 4、设置GPIO1_IO03为输出 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3);
val |= (1 << 3);
writel(val, GPIO1_GDIR);
/* 5、默认关闭LED */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
/* 6、注册字符设备驱动 */
retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
if(retvalue < 0){
printk("register chrdev failed!\r\n");
return -EIO;
}
return 0;
}
static void __exit led_exit(void)
{
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
unregister_chrdev(LED_MAJOR, LED_NAME);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO1_DR); // 读取GPIO1数据寄存器
val &= ~(1 << 3); // 清除bit3,输出低电平(LED亮)
writel(val, GPIO1_DR); // 写回寄存器
}else if(sta == LEDOFF) {
val = readl(GPIO1_DR);
val |= (1 << 3); // 置位bit3,输出高电平(LED灭)
writel(val, GPIO1_DR);
}
}
val &= ~(1 << 3):将bit3清零,GPIO输出低电平,LED点亮(低电平有效)val |= (1 << 3):将bit3置1,GPIO输出高电平,LED熄灭驱动加载时执行的初始化流程:
ioremap将5个寄存器的物理地址映射为虚拟地址CCM_CCGR1寄存器的bit[27:26],使能GPIO1时钟SW_MUX寄存器写入5,将GPIO1_IO03复用为GPIO功能SW_PAD寄存器写入0x10B0,配置驱动强度和上下拉GPIO1_GDIR的bit3置1,配置为输出模式GPIO1_DR的bit3置1,默认关闭LEDregister_chrdev注册设备号200,设备名"led"驱动卸载时执行的清理流程:
iounmap释放5个虚拟地址unregister_chrdev释放设备号200#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开led驱动 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]);
/* 向/dev/led文件写入数据 */
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}
retvalue = close(fd);
if(retvalue < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
./ledApp /dev/led 1 // 打开LED
./ledApp /dev/led 0 // 关闭LED
程序执行流程:open("/dev/led") → write(fd, &1, 1) → close(fd)
write系统调用触发内核led_write函数,通过copy_from_user获取用户数据,调用led_switch控制GPIO电平。
I.MX6U-ALPHA开发板上的蜂鸣器通过SNVS_TAMPER1引脚控制,复用为GPIO5_IO01。蜂鸣器驱动原理与LED类似,都是控制IO输出高低电平。
| 项目 | 说明 |
|---|---|
| 控制引脚 | SNVS_TAMPER1 → GPIO5_IO01 |
| 有效电平 | 低电平鸣叫(0=开,1=关) |
| 驱动方式 | GPIO推挽输出 |
| 设备类型 | 有源蜂鸣器(电平驱动即可鸣叫) |
[!note] 关于设备树极性 💡 蜂鸣器的实际有效电平为低电平:驱动内
gpio_set_value(dev->beep_gpio, 0)直接输出低电平开启蜂鸣器、输出高电平关闭,并未使用gpio_set_value_cansleep或极性反转逻辑。
蜂鸣器控制本质上是GPIO输出高低电平:
蜂鸣器设备树配置需要:pinctrl信息、设备节点、GPIO属性。驱动程序使用of_find_node_by_path和of_get_named_gpio获取GPIO编号,通过gpio_set_value控制电平。
在imx6ull-alientek-emmc.dts的iomuxc节点imx6ul-evk子节点下添加:
pinctrl_beep: beepgrp {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10B0 /* beep */
>;
};
| 属性 | 说明 |
|---|---|
MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 |
宏定义,将SNVS_TAMPER1复用为GPIO5_IO01 |
0x10B0 |
IO属性值 |
宏定义位于arch/arm/boot/dts/imx6ull-pinfunc-snvs.h。
在根节点"/"下创建蜂鸣器设备节点:
beep {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-beep";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_beep>;
beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;
status = "okay";
};
| 属性 | 说明 |
|---|---|
compatible |
匹配驱动字符串"atkalpha-beep" |
pinctrl-0 |
引用beepgrp pinctrl配置 |
beep-gpio |
蜂鸣器使用的GPIO,gpio5的第1个引脚 |
配置蜂鸣器前必须检查:
验证方法:编译设备树后启动系统,查看/proc/device-tree/beep节点是否存在。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define BEEP_CNT 1
#define BEEP_NAME "beep"
#define BEEPOFF 0
#define BEEPON 1
struct beep_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
int beep_gpio;
};
struct beep_dev beep;
static int beep_open(struct inode *inode, struct file *filp)
{
filp->private_data = &beep;
return 0;
}
static ssize_t beep_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char beepstat;
struct beep_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
beepstat = databuf[0];
if(beepstat == BEEPON) {
gpio_set_value(dev->beep_gpio, 0); /* 打开蜂鸣器 */
} else if(beepstat == BEEPOFF) {
gpio_set_value(dev->beep_gpio, 1); /* 关闭蜂鸣器 */
}
return 0;
}
static int beep_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations beep_fops = {
.owner = THIS_MODULE,
.open = beep_open,
.write = beep_write,
.release = beep_release,
};
static int __init beep_init(void)
{
int ret = 0;
/* 1、获取设备节点 */
beep.nd = of_find_node_by_path("/beep");
if(beep.nd == NULL) {
printk("beep node not find!\r\n");
return -EINVAL;
} else {
printk("beep node find!\r\n");
}
/* 2、获取GPIO属性,得到GPIO编号 */
beep.beep_gpio = of_get_named_gpio(beep.nd, "beep-gpio", 0);
if(beep.beep_gpio < 0) {
printk("can't get beep-gpio");
return -EINVAL;
}
printk("beep-gpio num = %d\r\n", beep.beep_gpio);
/* 3、设置GPIO为输出,高电平默认关闭 */
ret = gpio_direction_output(beep.beep_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (beep.major) {
beep.devid = MKDEV(beep.major, 0);
register_chrdev_region(beep.devid, BEEP_CNT, BEEP_NAME);
} else {
alloc_chrdev_region(&beep.devid, 0, BEEP_CNT, BEEP_NAME);
beep.major = MAJOR(beep.devid);
beep.minor = MINOR(beep.devid);
}
printk("beep major=%d,minor=%d\r\n", beep.major, beep.minor);
/* 2、初始化cdev */
beep.cdev.owner = THIS_MODULE;
cdev_init(&beep.cdev, &beep_fops);
/* 3、添加cdev */
cdev_add(&beep.cdev, beep.devid, BEEP_CNT);
/* 4、创建类 */
beep.class = class_create(THIS_MODULE, BEEP_NAME);
if (IS_ERR(beep.class)) {
return PTR_ERR(beep.class);
}
/* 5、创建设备 */
beep.device = device_create(beep.class, NULL, beep.devid, NULL, BEEP_NAME);
if (IS_ERR(beep.device)) {
return PTR_ERR(beep.device);
}
return 0;
}
static void __exit beep_exit(void)
{
cdev_del(&beep.cdev);
unregister_chrdev_region(beep.devid, BEEP_CNT);
device_destroy(beep.class, beep.devid);
class_destroy(beep.class);
}
module_init(beep_init);
module_exit(beep_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
蜂鸣器驱动初始化流程:
of_find_node_by_path("/beep")通过路径查找设备树节点of_get_named_gpio(beep.nd, "beep-gpio", 0)从设备树属性中获取GPIO编号gpio_direction_output(beep.beep_gpio, 1)设为输出,默认高电平关闭蜂鸣器alloc_chrdev_region自动分配主次设备号cdev_init关联cdev与file_operationsclass_create和device_create自动在/dev下创建设备节点static ssize_t beep_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
struct beep_dev *dev = filp->private_data;
// ...
if(beepstat == BEEPON) {
gpio_set_value(dev->beep_gpio, 0); // 低电平=蜂鸣器响
} else if(beepstat == BEEPOFF) {
gpio_set_value(dev->beep_gpio, 1); // 高电平=蜂鸣器停
}
}
通过filp->private_data获取设备结构体指针,使用GPIO子系统API gpio_set_value控制电平。
| 对比项 | LED驱动 | 蜂鸣器驱动 |
|---|---|---|
| 设备节点获取 | 硬编码路径 | of_find_node_by_path |
| GPIO获取方式 | 直接寄存器操作 | of_get_named_gpio + gpio_set_value |
| 设备号分配 | register_chrdev(静态) |
alloc_chrdev_region(动态) |
| 设备节点创建 | 手动mknod |
class_create + device_create自动创建 |
| private_data | 未使用 | open时设置,write时使用 |
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define BEEPOFF 0
#define BEEPON 1
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开beep驱动 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
/* 向/dev/beep文件写入数据 */
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){
printf("BEEP Control Failed!\r\n");
close(fd);
return -1;
}
retvalue = close(fd); /* 关闭文件 */
if(retvalue < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
Makefile核心内容:
KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := led.o # LED驱动改为beep.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译命令:
make -j32 # 编译驱动模块
arm-linux-gnueabihf-gcc ledApp.c -o ledApp # 编译测试APP
LED驱动测试:
# 关闭心跳灯(出厂系统需要)
echo none > /sys/class/leds/sys-led/trigger
# 加载驱动
depmod # 首次加载需要
modprobe led.ko
# 创建设备节点(非设备树方式)
mknod /dev/led c 200 0
# 测试
./ledApp /dev/led 1 # 点亮LED
./ledApp /dev/led 0 # 熄灭LED
# 卸载驱动
rmmod led.ko
蜂鸣器驱动测试:
# 加载驱动
depmod
modprobe beep.ko
# 测试
./beepApp /dev/beep 1 # 蜂鸣器鸣叫
./beepApp /dev/beep 0 # 蜂鸣器停止
# 卸载驱动
rmmod beep.ko
./ledApp /dev/led 1后红色LED点亮,执行./ledApp /dev/led 0后熄灭./beepApp /dev/beep 1后蜂鸣器鸣叫,执行./beepApp /dev/beep 0后停止beep node find!,GPIO5_IO01 对应的 GPIO 编号为 129/proc/device-tree可验证设备树节点(gpioled/beep)是否正确加载⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。
| 特性 | I.MX6ULL (Linux) | STM32 (裸机/HAL) | RK3568 (Linux) |
|---|---|---|---|
| GPIO API | gpio_set_value |
HAL_GPIO_WritePin |
gpio_set_value |
| 设备树 | 支持 | 不支持 | 支持 |
| 时钟使能 | 设备树+驱动配置 | __HAL_RCC_GPIOx_CLK_ENABLE |
设备树+驱动配置 |
| 引脚复用 | pinctrl子系统 | GPIO_InitTypeDef |
pinctrl子系统 |
| 并发安全 | 内核保证 | 需手动处理 | 内核保证 |
| 设备节点 | 自动/手动创建 | 无(直接操作寄存器) | 自动创建 |
| 调试方式 | printk/dmesg |
串口打印/LED闪烁 | printk/dmesg |
| 中断支持 | request_irq |
HAL_NVIC_EnableIRQ |
request_irq |
核心差异总结:
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。
考察点:MMU与地址映射
参考答案:
Linux开启MMU后,CPU访问的都是虚拟地址,直接操作物理地址会导致内核崩溃。ioremap将外设寄存器的物理地址映射到内核虚拟地址空间,驱动通过映射后的虚拟地址访问寄存器。iounmap在驱动卸载时释放映射,避免虚拟地址空间泄漏。对于I.MX6ULL,寄存器物理地址如0X020E0068必须先映射为虚拟地址才能操作。
考察点:设备类型选择
参考答案:
misc_register注册,代码量少。alloc_chrdev_region)、初始化cdev、创建类和设备节点,适合复杂设备或多设备驱动。考察点:设备树匹配机制
参考答案:
compatible属性是设备树匹配驱动的核心字符串。驱动中通过of_match_table定义支持的compatible字符串列表,内核在加载驱动时遍历设备树节点的compatible属性,找到匹配项后调用驱动的probe函数。匹配过程由内核总线层完成。例如蜂鸣器驱动的compatible为"atkalpha-beep",设备树中节点的compatible也必须是"atkalpha-beep"才能匹配成功。
考察点:GPIO子系统优势
参考答案:
考察点:内核与用户空间数据交互
参考答案:
copy_from_user将用户空间数据安全复制到内核空间。不能直接访问用户空间指针,原因:①用户空间地址可能无效或已被交换到swap,直接访问会导致内核page fault崩溃;②用户空间地址可能没有内核要求的访问权限;③直接访问无法处理用户空间地址重映射的情况。copy_from_user内部会进行地址有效性检查和异常处理,确保内核安全。其返回值为未复制的字节数,0表示成功。
代码来源:正点原子I.MX6U嵌入式Linux驱动开发指南 V2.0.1(第四十一章、第四十六章) 内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第41章 嵌入式Linux LED驱动开发实验、第46章 Linux蜂鸣器实验 最后更新: 2026-09-17