ARM嵌入式学习笔记——Linux字符设备驱动程序设计(三)

简介: ARM嵌入式学习笔记——Linux字符设备驱动程序设计

Linux字符设备驱动硬件操作接口之read接口

回顾应用程序read函数

ssize_t read(int fd, void *buf, size_t count);


  • 功能:从硬件读取数据放到用户缓冲区
  • 参数:

fd:设备文件描述符,它是字符设备文件代理

buf:传递用户缓冲区的首地址。

count:传递要读取的字节数。

返回值:返回实际读取的字节数。

对于底层驱动的read函数接口

ssize_t (*read)(struct file *file, char __user *buf, size_t count, loff_t *ppos);


  • 调用关系:应用read->C库read->软中断->内核sys_read->驱动read。
  • 接口功能:从硬件读取数据给应用程序起到桥梁的作用,连接应用和硬件。
  • 参数:

file:文件指针,跟应用read的第一个参数有关。

buf:此指针变量用“__user”修饰,所以必须保存的是用户缓存区首地址。所以将来底层驱动的read接口可以从硬件读取的数据通过buf放到用户缓冲区,

(*buf = 250,此操作极其不安全),两种情况:

1、应用程序故意传递非法地址,例如read(fd, NULL,0);

2、用户缓冲区对应的虚拟内存映射无效,造成地址非法访问。

驱动read接口要想通过buf来操作用户缓冲区必须利用内核提供的内存拷贝函数:

int copy_to_user(void __user *to, void *from, int n);

功能:将内核缓冲区的数据拷贝到用户缓冲区。

to:目的地址,用户缓冲区首地址。

from:源地址,内核缓冲区首地址。

n:要拷贝的字节数。

count:传递要读取的数据字节数,等于应用read的第三个参数。

ppos:记录上一次的读位置,如果驱动read采用连续多次读取。

1、先获取上一次read的读位置,unsigned long pos = *pos;

2、假设这次read又读取了100字节,底层驱动read返回之前记得更新读位置,*ppos = pos + 100;

注意:如果读一次搞定无需关注ppos。

案例:编写LED字符设备驱动,不仅仅实现开关某个灯,还能获取灯的开关状态。

  • 实验步骤同上。
  • led_test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//声明描述led操作信息的数据结构
struct led_event {
    int cmd;//开灯命令,1-开灯,0-关灯。
    int index; //灯编号
};
//声明描述led等状态的数据结构
struct led_state{
    int index;//灯编号。
    int state;//灯状态,1-关灯,0-开灯。
};
int main()
{
    int fd;
    struct led_event led;//分配用户缓冲区,保存灯的操作信息。
    struct led_state ledst;//分配用户缓冲区,保存灯的状态。
    if(argc != 3){
        printf("usage: %s <on|off> <1|2|3|4> \n", argv[0]);
        return -1;
    }
    fd = open("/dev/myled", O_RDWR);
    if(fd < 0)
        return -1;
    if(!strcmp(argv[1],  "on"))
        led.cmd = 1;
    else if(!strcmp(argv[1],  "off"))
        led.cmd = 0;
    led.index = strtoul(argv[2], NULL, 0);
    //将LED操作信息发生给驱动,开关灯。
    write(fd, &led, sizeof(led));
    //获取灯的状态
    //为ledst 赋值。
    ledst.index = strtol(argv[2], NULL, 0);
    read(fd, &ledst, sizeof(ledst));
    //打印状态
    printf("%d led state is %s \n", ledst.index, ledst.state? "close":"open");
    close(fd);
    return 0;
}



  • led_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <mach/platform.h>
//声明描述led操作信息的数据结构
struct led_event {
    int cmd;//开灯命令,1-开灯,0-关灯。
    int index; //灯编号
};
//声明描述led等状态的数据结构
struct led_state{
    int index;//灯编号。
    int state;//灯状态,1-关灯,0-开灯。
};
//声明描述LED硬件信息数据结构
struct led_resource{
    char *name;
    int gpio;
};
//初始化LED硬件信息对象
static struct led_resource led_info[] = {
    {
        .name = "LED1",
        .gpio = PAD_GPIO_C + 12
    },
    {
        .name = "LED2",
        .gpio = PAD_GPIO_C + 17
    },
    {
        .name = "LED3",
        .gpio = PAD_GPIO_C + 11
    },
    {
        .name = "LED4",
        .gpio = PAD_GPIO_B + 26
    }
};
//向硬件设备写入数据接口
//参数对应关系
//write的fd  <->led_write file;  write的buf  <-> led_write  buf;
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    int i;
    //分配内核缓冲区,暂存从用户缓冲区获取的数据
    int kcmd;
    //拷贝用户缓冲区的数据到内核缓冲区中
    //kcmd = *(int *)buf; <---危险操作,使用操作函数
    copy_from_user(&cmd, buf, sizeof(kcmd));
    //操作硬件
    for(i = 0; i< ARRAY_SIZE(led_info); i++)
    {
        gpio_set_value(led_info[i].gpio, !kcmd);
        printk("%s: %s 第 %d 个灯", __func, kcmd ? "开":"关", i+1);
    }
    return count
}
//读硬件接口
static ssize_t led_read(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    //1、分配内核缓冲区,暂存数据
    struct led_state kledst;
    //2.先从用户缓存区拷贝数据到内核缓冲区
    copy_from_user(&kledst, buf, sizeof(kledst));
    kledst.state = gpio_get_value(led_info[kledst.index-1].gpio);
    //将内核缓存区中的有效数据拷贝到用户缓冲区
    copy_to_user(buf, &kledst, sizeof(kledst));
    return count;
}
//定义初始化LED硬件操作接口对象
static struct file_operations led_fops = {
    .write = led_write//向硬件写入数据
    .read = led_read;//从硬件读取数据
}
//定义设备号对象
static dev_t dev;
//定义字符设备对象
static struct cdev led_cdev;
static int led_init(void)
{
    int i;
    //申请GPIO资源,配置为输出,输出1
    for(i = 0; i < ARRAY_SIZE(led_info); i++)
    {
        gpio_request(led_info[i].gpio, led_info[i].name);
        gpio_direction_output(led_info[i].gpio, 1);
    }
    //申请设备号.
    alloc_chrdev_region(&dev, 0, 1, "myled");
    //初始化字符设备对象,添加操作接口
    cdev_init(&led_cdev, &led_fops);
    //向内核注册字符设备对象。
    cdev_add(&led_cdev, dev, 1);
    return 0;
}
static void led_exit(void)
{
    //从内核卸载字符设备对象
    cdev_del(&led_cdev);
    //释放设备号
    unregister_chrdev_region(dev, 1);
    //释放GPIO资源,输出1
    for(i = 0; i < ARRAY_SIZE(led_info); i++)
    {
        gpio_set_value(led_info[i].gpio, 1);
        gpio_free(led_info[i].gpio);
    }
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");


字符设备驱动硬件操作接口之ioctl

学习掌握ioctl系统调用函数

int ioctl(int fd, int request, …);


  • 函数功能:

应用程序利用此函数可以向硬件设备发生控制命令略带写write意味。

应用程序利用此函数可以和硬件进行读或者写操作,简直侵略read/write的地位。

参数:

fd:设备文件描述符。

cmd:向硬件设备发送的控制命令扩展成任何命令都可以,命令又驱动工程师自行定义。

将来驱动程序可以利用第三个参数可以读写用户缓冲区。

返回值:成功返回0,失败返回-1.

对应底层驱动的ioctl接口:

struct file_operations {
    long (*unlocked_ioctl)(struct file *file , unsigned int cmd, unsigned long buf);
}


  • 调用关系:应用程序ioctl->C库的ioctl->软中断->内核sys_ioctl->驱动ioctl接口。
  • 接口功能:

驱动可以向硬件设备发送控制命令,略带写write意味。

驱动还可以和硬件进行读写操作。

  • 参数:

file:文件指针,与fd有关。

cmd:保存应用程序传递的控制命令

buf:如果应用要和硬件进行读写操作,buf一般保存用户缓冲区的首地址(当然也可以保存一些普通的变量),所以将来底层驱动的ioctl接口可以通过buf对用户缓冲区进行读写访问,但是使用时注意数据类型的强转换。

具体使用buf时使用copy_from_user(&kdata, (int *)buf, 4);

例如:ioctl(fd, LED_ON);//仅仅向设备发送开灯命令。

ioctl(fd, MMA8653_WRITE, &data);//除了发送命令还要写入数据。

返回值:成功返回0,失败返回-1。

案例:编写LED字符设备驱动,实现开关灯、开关某个灯。

下位机测试:

cd /home/drivers
insmod led_drv.ko
cat /proc/devices
mknod /dev/myled c 244 0
./led_test on
./led_test off
./led_test on 1
./led_test off 1


代码实现:实现开关灯

  • led_test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//自定义两个命令
#define LED_ON  0x100001
#define LED_OFF 0x100002
//声明描述led操作信息的数据结构
struct led_event {
    int cmd;//开灯命令,1-开灯,0-关灯。
    int index; //灯编号
};
//声明描述led等状态的数据结构
struct led_state{
    int index;//灯编号。
    int state;//灯状态,1-关灯,0-开灯。
};
int main()
{
    int fd;
    struct led_event led;//分配用户缓冲区,保存灯的操作信息。
    struct led_state ledst;//分配用户缓冲区,保存灯的状态。
    if(argc != 3){
        printf("usage: %s <on|off> <1|2|3|4> \n", argv[0]);
        return -1;
    }
    fd = open("/dev/myled", O_RDWR);
    if(fd < 0)
        return -1;
    if(!strcmp(argv[1],  "on"))
        ioctl(fd, LED_ON);
    else if(!strcmp(argv[1],  "off"))
        ioctl(fd, LED_OFF);
    close(fd);
    return 0;
}


  • led_drv.c
//自定义两个命令
#define LED_ON  0x100001
#define LED_OFF 0x100002
//控制操作接口
static long led_ioctl(struct file *file, unsigned long cmd, unsigned long buf)
{
    int i;
    switch(cmd){
        case LED_ON:
            for(i = 0; i < ARRAY_SIZE(led_info); i++){
                gpio_set_value(led_info[i].gpio, 0);
                printk("%s : turn on %d led. \n", __func__, i+1);
            }
            break;
        case LED_OFF:
            for(i = 0; i < ARRAY_SIZE(led_info); i++){
                gpio_set_value(led_info[i].gpio, 1);
                printk("%s : turn off %d led. \n", __func__, i+1);
            }
            break;
        default:
            printk("command is invalid\n");
            return -1;
    }
    retrun 0;
}
//部分代码:
static struct file_operations led_fops = {
    .unlocked_ioctl = led_ioctl
};


代码实现:实现开关某个灯

• led_test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//自定义两个命令
#define LED_ON  0x100001
#define LED_OFF 0x100002
//声明描述led操作信息的数据结构
struct led_event {
    int cmd;//开灯命令,1-开灯,0-关灯。
    int index; //灯编号
};
//声明描述led等状态的数据结构
struct led_state{
    int index;//灯编号。
    int state;//灯状态,1-关灯,0-开灯。
};
int main()
{
    int fd;
    int index;
    struct led_event led;//分配用户缓冲区,保存灯的操作信息。
    struct led_state ledst;//分配用户缓冲区,保存灯的状态。
    if(argc != 3){
        printf("usage: %s <on|off> <1|2|3|4> \n", argv[0]);
        return -1;
    }
    fd = open("/dev/myled", O_RDWR);
    if(fd < 0)
        return -1;
    //获取灯编号。
    index = strtoul(argv[2], NULL, 0);
    if(!strcmp(argv[1],  "on"))
        ioctl(fd, LED_ON, &index);//三个参数
    else if(!strcmp(argv[1],  "off"))
        ioctl(fd, LED_OFF, &index);
    close(fd);
    return 0;
}


  • led_drv.c
//自定义两个命令
#define LED_ON  0x100001
#define LED_OFF 0x100002
//控制操作接口
static long led_ioctl(struct file *file, unsigned long cmd, unsigned long buf)
{
    int i;
    //分配内核缓冲区
    int kindex;
    copy_from_user(&kindex, (int *)buf, sizeof(kindex));
    switch(cmd){
        case LED_ON:
                gpio_set_value(led_info[kindex - 1].gpio, 0);
                printk("%s : turn on %d led. \n", __func__, kindex);
            break;
        case LED_OFF:
                gpio_set_value(led_info[kindex - 1].gpio, 1);
                printk("%s : turn off %d led. \n", __func__, kindex);
            break;
        default:
            printk("command is invalid\n");
            return -1;
    }
    retrun 0;
}
//部分代码:
static struct file_operations led_fops = {
    .unlocked_ioctl = led_ioctl
};


了解两个数据结构:struct inode和struct file

struct inode{
    umode_t i_mode;
    unsigned short i_opflags;
    uid_t i_uid;
    gid_t i_gid;
    unsigned int i_flags;
    struct posix_acl *i_acl;
    struct posix_acl *i_default_acl;
    const struct inode_operations *i_op;
    struct super_block *i_sb;
    struct address_spacc *i_mapping;
    void *i_security;
    ....
    dev_t i_rdev;//如果此文件是设备文件,i_rdev保存对应的设备号。
    struct cdev *i_cdev;//指向设备文件对应的字符设备驱动对象。
    ....
};


  • 功能:描述一个文件的物理信息(权限,用户和组、大小、日期)
  • 生命周期:每当创建一个文件时(touch,mknod,echo,vim等),Linux内核就会为此文件定义初始化一个inode对象来描述新文件的物理属性信息,每当删除文件(rm),内核就会删除之前创建的inode对象。
  • 一个文件仅有唯一一个inode对象。
  • 结论:硬件操作接口open/release中的第一个形参inode指针指向内核创建的Inode对象,将来驱动程序可以利用inode来获取文件的物理信息,例如:设备号
int led_open(struct inode *inode, ...)
{
    printk("主设备号  %d, 次设备号  %d\n", MAJOR(inode->i_rdev, MINOR(indoe->i_rdev));
    return 0;
}


结构体file

struct file{
    struct file_operations *f_op;
}


  • 功能:描述一个文件被成功打开open之后的属性。
  • 生命周期:打开文件open成功,内核就会创建一个file对象来描述文件打开后的属性,当close文件后,内核就会销毁对应的file对象。
  • 注意:一个文件可以有多个file对象。
  • 成员:
  • f_op:指向字符设备对象操作结构体。
  • 拔高:了解内核实现原理:应用通过系统调用号找到内核函数sys_open,sys_open创建一个file对象,然后将之前通过inode找到的字符设备对象中的操作接口对象的地址给了file.f_op,最终sys_open返回一个fd,并且内核将fd和创建file建立亲戚关系。
  • 将来其余系统调用函数:read/write/ioctl/close等访问都是通过fd找到相应的执行函数。找到驱动的流程:
  • read(fd)->找到file->可以访问file.f_op->访问驱动函数->xxx_read

案例:编写LED字符设备驱动,利用ioctl开关灯,此时将四个LED作为四个硬件个体。

分析思路:

  • 四个LED灯物理特性一致,所以只需要一个驱动。
  • 一个驱动主设备号一个
  • 四个硬件四个次设备号:0/1/2/3
  • 四个硬件设备文件四个:myled0,myled1,myled2,myled3.
  • 四个设备文件四个inode:inode0, inode1, inode2, inode3
  • 如果应用程序将四个灯全部打开:fd设备文件描述符:file0, file1, file2, file3
  • 终极对应关系:
  • fd0->file0->inode0->i_rdev0->主设备号A,次设备号0。
  • fd1->file1->inode1->i_rdev1->主设备号A,次设备号1。
  • fd2->file2->inode2->i_rdev2->主设备号A,次设备号2。
  • fd3->file3->inode3->i_rdev3->主设备号A,次设备号3。
  • cdev对象:一个led_cdev。
  • 操作接口对象:一个led_fops,操作接口共用。
  • 问:共用的操作接口函数如区分某个硬件设备呢?
  • 答:通过文件描述符下的设备文件。
long led_ioctl(struct file *file, cmd, buf)
{
    struct inode *inode = file->f_path.dentry->d_inode;
    int minor = MINOR(inode->i_rdev);
    led_info(minor);
}


代码实现:

  • led_test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//自定义两个命令
#define LED_ON  0x100001
#define LED_OFF 0x100002
//声明描述led操作信息的数据结构
struct led_event {
    int cmd;//开灯命令,1-开灯,0-关灯。
    int index; //灯编号
};
//声明描述led等状态的数据结构
struct led_state{
    int index;//灯编号。
    int state;//灯状态,1-关灯,0-开灯。
};
int main()
{
    int fd0, fd1, fd2, fd3;
    int index;
    struct led_event led;//分配用户缓冲区,保存灯的操作信息。
    struct led_state ledst;//分配用户缓冲区,保存灯的状态。
    if(argc != 3){
        printf("usage: %s <on|off> <1|2|3|4> \n", argv[0]);
        return -1;
    }
    fd0 = open("/dev/myled0", O_RDWR);
    if(fd0 < 0)
        return -1;
    fd1 = open("/dev/myled1", O_RDWR);
    if(fd1 < 0)
        return -1;
    fd2 = open("/dev/myled2", O_RDWR);
    if(fd2 < 0)
        return -1;
    fd3 = open("/dev/myled3", O_RDWR);
    if(fd3 < 0)
        return -1;
    while(1){
        ioctl(fd0, LED_ON);
        sleep(1);
        ioctl(fd1, LED_ON);
        sleep(1);
        ioctl(fd2, LED_ON);
        sleep(1);
        ioctl(fd3, LED_ON);
        sleep(1);
        ioctl(fd0, LED_OFF);
        sleep(1);
        ioctl(fd1, LED_OFF);
        sleep(1);
        ioctl(fd2, LED_OFF);
        sleep(1);
        ioctl(fd3, LED_OFF);
        sleep(1);
    }
    close(fd0);
    close(fd1);
    close(fd2);
    close(fd3);
    return 0;
}


  • led_drv.c


//自定义两个命令
#define LED_ON  0x100001
#define LED_OFF 0x100002
//控制操作接口
static long led_ioctl(struct file *file, unsigned long cmd, unsigned long buf)
{
    int i;
    //通过file找到对应inode
    struct inode *inode = file->f_path.dentry->d_inode;
    int minor = MINOR(inode->i_rdev);
    //分配内核缓冲区
    switch(cmd){
        case LED_ON:
                gpio_set_value(led_info[minor].gpio, 0);
                printk("%s : turn on %d led. \n", __func__, minor + 1);
            break;
        case LED_OFF:
                gpio_set_value(led_info[minor].gpio, 1);
                printk("%s : turn off %d led. \n", __func__, minor + 1);
            break;
        default:
            printk("command is invalid\n");
            return -1;
    }
    retrun 0;
}
//定义初始化LED硬件操作接口对象
//共用
static struct file_operations led_fops = {
    .unlocked_ioctl = led_ioctl;
};
//定义设备号对象
static dev_t dev;
//定义字符设备对象
static struct cdev led_cdev;
static int led_init(void)
{
    int i;
    for(i = 0; i < ARRAY_SIZE(led_info); i++){
        gpio_request(led_info[i].gpio, led_info[i].name);
        gpio_direction_output(led_info[i].gpio, 1);
    }
    //申请设备号
    //次设备号0,1,2,3
    alloc_chrdev_region(&dev, 0, 4, "myled");
    cdev_init(&led_cdev, &led_fops);
    cdev_add(&led_cdev, dev, 4);
    return 0;
}
static void led_exit(void)
{
    int i;
    cdev_del(&led_cdev);
    unregister_chrdev_region(dev, 4);
    for(i = 0; i < ARRAY_SIZE(led_info); i++){
        gpio_direction_output(led_info[i].gpio, 1);
        gpio_free(led_info[i].gpio);
    }
}
module_init(led_init);
module_exit(led_exit);
MODULE_LINCESE("GPL");


字符设备文件自动创建只需要三个保证+四个函数即可完成

  • 保证根文件系统rootfs必要脚本文件rcS中添加以下两句话:

  • mount -a:就是为了执行fstab文件。

  • echo /sbin/mdev > /proc/sys/kernel/hotplug

  • 说明:表面看是向文件hotplug写入字符串“/sbin/mdev”,本质是将来驱动要创建设备文件时,驱动自动解析hotplug文件,找到/sbin/mdev,并且执行此命令mdev,让mdev命令来帮驱动创建设备文件。
  • 保证根文件系统rootfs必须有mdev命令,执行在下位机执行:which is mdev查看是否存在即可。

  • 保证根文件系统rootfs必要配置文件fstab中必须有以下两句话:

  • proc /proc proc defaults 0 0
  • sysfs /sys sysfs defaults 0 0
  • 结论:将proc虚拟文件系统挂接到/proc目录下,将sysfs虚拟文件系统挂接到/sys目录下。

四个函数:给mdev提供参数数据。

  • class_create
  • device_create
  • device_destroy
  • class_destroy
struct class *cls; //创建一个设备类指针,类似苹果上长一个嫩芽。
//cls指向创建的设备对象。
cls = class_create(THIS_MODULE, "tarena1");
//正式创建设备文件,本质是将来调用mdev来创建设备文件。
//dev:设备文件的设备号,myled:设备文件名
device_create(cls, NULL, dev, NULL, "myled");
//删除设备文件
device_destroy(cls, dev);
//删除设备对象。
class_destroy(cls);


案例:

led_drv.c


//自定义两个命令
#define LED_ON  0x100001
#define LED_OFF 0x100002
//控制操作接口
static long led_ioctl(struct file *file, unsigned long cmd, unsigned long buf)
{
    int i;
    //通过file找到对应inode
    struct inode *inode = file->f_path.dentry->d_inode;
    int minor = MINOR(inode->i_rdev);
    //分配内核缓冲区
    switch(cmd){
        case LED_ON:
                gpio_set_value(led_info[minor].gpio, 0);
                printk("%s : turn on %d led. \n", __func__, minor + 1);
            break;
        case LED_OFF:
                gpio_set_value(led_info[minor].gpio, 1);
                printk("%s : turn off %d led. \n", __func__, minor + 1);
            break;
        default:
            printk("command is invalid\n");
            return -1;
    }
    retrun 0;
}
//定义初始化LED硬件操作接口对象
//共用
static struct file_operations led_fops = {
    .unlocked_ioctl = led_ioctl;
};
//定义设备号对象
static dev_t dev;
//定义字符设备对象
static struct cdev led_cdev;
struct class *cls; //创建一个设备类指针,类似苹果上长一个
static int led_init(void)
{
    int i;
    for(i = 0; i < ARRAY_SIZE(led_info); i++){
        gpio_request(led_info[i].gpio, led_info[i].name);
        gpio_direction_output(led_info[i].gpio, 1);
    }
    //申请设备号
    //次设备号0,1,2,3
    alloc_chrdev_region(&dev, 0, 4, "myled");
    cdev_init(&led_cdev, &led_fops);
    cdev_add(&led_cdev, dev, 4);
    //5.创建设备类对象
    cls = class_create(THIS_MODULE, "tarena1");
    //6、自动创建设备文件
    device_create(cls, NULL,  \
                MKDEV(MAJOR(dev), 0), NULL, "myled0");
    device_create(cls, NULL,  \
                MKDEV(MAJOR(dev), 1), NULL, "myled1");
    device_create(cls, NULL,  \
                MKDEV(MAJOR(dev), 2), NULL, "myled2");
    device_create(cls, NULL,  \
                MKDEV(MAJOR(dev), 3), NULL, "myled3");
    return 0;
}
static void led_exit(void)
{
    int i;
    cdev_del(&led_cdev);
    unregister_chrdev_region(dev, 4);
    for(i = 0; i < ARRAY_SIZE(led_info); i++){
        gpio_direction_output(led_info[i].gpio, 1);
        gpio_free(led_info[i].gpio);
    }
    //删除设备文件,
    device_destroy(cls, \
                MKDEV(MAJOR(dev), 0), 0);
    device_destroy(cls, \
                MKDEV(MAJOR(dev), 1), 1);
    device_destroy(cls, \
                MKDEV(MAJOR(dev), 2), 2);
    device_destroy(cls, \
                MKDEV(MAJOR(dev), 3), 3);
    //删除设备类对象
    class_destroy(cls);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LINCESE("GPL");


相关文章
|
11天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
66 13
|
4月前
|
NoSQL Unix Linux
Linux 设备驱动程序(一)(上)
Linux 设备驱动程序(一)
170 62
|
2月前
|
数据处理
基于ARM的嵌入式原理与应用:ALU的功能与特点
基于ARM的嵌入式原理与应用:ALU的功能与特点
210 0
|
3月前
|
Linux 程序员 编译器
Linux内核驱动程序接口 【ChatGPT】
Linux内核驱动程序接口 【ChatGPT】
|
4月前
|
存储 缓存 Unix
Linux 设备驱动程序(三)(上)
Linux 设备驱动程序(三)
56 3
|
4月前
|
缓存 安全 Linux
Linux 设备驱动程序(一)((下)
Linux 设备驱动程序(一)
51 3
|
4月前
|
NoSQL Linux C语言
嵌入式GDB调试Linux C程序或交叉编译(开发板)
【8月更文挑战第24天】本文档介绍了如何在嵌入式环境下使用GDB调试Linux C程序及进行交叉编译。调试步骤包括:编译程序时加入`-g`选项以生成调试信息;启动GDB并加载程序;设置断点;运行程序至断点;单步执行代码;查看变量值;继续执行或退出GDB。对于交叉编译,需安装对应架构的交叉编译工具链,配置编译环境,使用工具链编译程序,并将程序传输到开发板进行调试。过程中可能遇到工具链不匹配等问题,需针对性解决。
159 3
|
4月前
|
安全 数据管理 Linux
Linux 设备驱动程序(一)(中)
Linux 设备驱动程序(一)
37 2
|
4月前
|
Linux
Linux 设备驱动程序(四)
Linux 设备驱动程序(四)
35 1
|
4月前
|
存储 数据采集 缓存
Linux 设备驱动程序(三)(中)
Linux 设备驱动程序(三)
53 1