嵌入式 LINUX 驱动开发 day02 字符设备驱动 字符设备驱动 虚拟串口, 一个驱动支持多个设备

news2024/10/7 16:15:55

1. 驱动开发 字符设备驱动

代码: 

vser.c

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>   
/***** 设备相关信息 ******/
static unsigned int VSER_MAJOR = 256;      //主设备号
static unsigned int VSER_MINOR = 0;        //次设备号
#define VSER_DEV_CNT   1        //个数为1个
#define VSER_DEV_NAME  "vser"   //设备名称

/***** 字符设备注册步骤 ***************************************************
 *        初始化函数
 * 1.将主设备和次设备组合成设备号   MKDEV(主设备号,次设备号)
 * 2.将该设备号注册到内核         register_chrdev_region(设备号,设备个数,设备名称) 或 
 *        注销函数
 * 1.将申请的设备号注销           unregister_chrdev_region(设备号,设备个数) 
 * 
 * 跟文件系统下创建 设备文件 cat /proc/devices  
 * 查看到设备信息  
            Character devices:
            1 mem
            256 vser   我们自己申请的设备信息
            4 /dev/vc/0
            4 tty
            4 ttyS
            5 /dev/tty
            5 /dev/console  
 * ***********************************************************************/

static int __init vser_init(void)
{
    int ret;    //用于获取函数返回值,并该函数是否执行成功

    dev_t dev;  //用于存储设备号
    dev = MKDEV(VSER_MAJOR,VSER_MINOR); //组合成设备号 (VSER_MAJOR << 20) | VSER_MINOR

    ret = register_chrdev_region(dev,VSER_DEV_CNT,VSER_DEV_NAME);
    if(ret != 0)
    {
        printk("静态申请设备号失败\n");

        ret = alloc_chrdev_region(&dev,VSER_MINOR,VSER_DEV_CNT,VSER_DEV_NAME);
        if(ret != 0)
        {
            printk("动态申请设备号失败\n");
            goto register_err;
        }
        VSER_MAJOR = MAJOR(dev);    //从设备号中提取主设备号
        VSER_MINOR = MINOR(dev);    //从设备号中提取次设备号
    }

    return 0;   //表示成功,直接结束函数并返回0

register_err:   /* 设备号申请失败 */
    return ret;
    
}

static void __exit vser_exit(void)
{
    dev_t dev;  //用于存储设备号
    dev = MKDEV(VSER_MAJOR,VSER_MINOR); //组合成设备号 (VSER_MAJOR << 20) | VSER_MINOR

    unregister_chrdev_region(dev,VSER_DEV_CNT);
}

/****** 加入到内核 *******/
module_init(vser_init);
module_exit(vser_exit);
/****** 模块说明 ********/
MODULE_LICENSE("GPL");  /*开元许可协议:GPL协议*/

Makefile

#动态编译内核驱动生成.ko文件的Makeifle

#自己的模块代码名
obj-m = vser.o	#就会生成一个 vser.ko 文件

#内核源代码路径
ifeq ($(ARCH),arm)
	KERNELDIR ?= /home/student/linux-5.4.31
else
	KERNELDIR ?= /lib/modules/${shell uname -r}/build
endif

#当前模块路径
PWD ?= $(shell pwd)

#编译源码生成 .ko 文件 make all
all:
	${MAKE} -C ${KERNELDIR} M=${PWD} modules
#伪代码之清除垃圾
clean:
	rm Module.* modules.* *.mod *.ko


1.  命令:  make    (编译形成vser .ko 文件)


2.  命令 :  mknod  /dev/vser0  c  256 0        (创建一个字符设备)

解释
mknod  == make node  //创建一个节点
/dev/vser0  设备名称
c   字符设备
256  主设备号
0    次设备号

 


3. 命令  :  ls -l  /dev/vser0(设备查看存在)


4.命令:  sudo rmmod vser  (卸载模块   防止之前的模块没有卸载)

 


5. 命令 :  sudo  insmod vser.ko   (加载  模块)


 6. 命令 :  lsmod   ( 查看模块是否加载)

  


7. 命令:  dmesg  (查看  内核模块的输出   比如说  printk)(这个没有内核信息打印)


 8. 过程多差不多!!!!    (下面的 我将不写过程了,,除了一个驱动 多个设备)


2. 字符设备注册

代码: 

vser.c

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>   
#include <linux/cdev.h>
/***** 设备相关信息 ******/
static unsigned int VSER_MAJOR = 256;      //主设备号
static unsigned int VSER_MINOR = 0;        //次设备号
#define VSER_DEV_CNT   1        //个数为1个
#define VSER_DEV_NAME  "vser"   //设备名称

/**** 字符设备结构体 ******/
static struct cdev vser_cdev;   //字符设备结构体

/**** 设备操作相关函数 到时候就可以使用 open 打开该设备文件 *****/
int vser_open(struct inode *p_inode, struct file *p_file)
{
    printk("vser设备打开了\n");
    return 0;
}

/**** 设备操作集 *****/
static struct file_operations vser_ops = {  //设备操作集结构体
    .owner = THIS_MODULE,
    .open = vser_open,
};


/***** 字符设备注册步骤 ***************************************************
 *        初始化函数
 * 1.将主设备和次设备组合成设备号     MKDEV(主设备号,次设备号)
 * 2.将该设备号注册到内核           register_chrdev_region(设备号,设备个数,设备名称) 或 
 * 3.初始化字符设备                cdev_init(字符设备结构体地址,操作集结构体地址)
 * 4.将字符设备添加到内核散列表map中  cdev_add(字符设备结构体地址,设备号,设备个数)
 *        注销函数
 * 1.从内核散链表map中删除字符设备    cdev_del(字符设备结构体地址)
 * 2.将申请的设备号注销              unregister_chrdev_region(设备号,设备个数) 
 * 
 * 跟文件系统下创建 设备文件 cat /proc/devices  
 * 查看到设备信息  
            Character devices:
            1 mem
            256 vser   我们自己申请的设备信息
            4 /dev/vc/0
            4 tty
            4 ttyS
            5 /dev/tty
            5 /dev/console  
 * ***********************************************************************/

static int __init vser_init(void)
{
    int ret;    //用于获取函数返回值,并该函数是否执行成功

    /*********  申请设备号 **************/
    dev_t dev;  //用于存储设备号
    dev = MKDEV(VSER_MAJOR,VSER_MINOR); //组合成设备号 (VSER_MAJOR << 20) | VSER_MINOR

    ret = register_chrdev_region(dev,VSER_DEV_CNT,VSER_DEV_NAME);
    if(ret != 0)
    {
        printk("静态申请设备号失败\n");

        ret = alloc_chrdev_region(&dev,VSER_MINOR,VSER_DEV_CNT,VSER_DEV_NAME);
        if(ret != 0)
        {
            printk("动态申请设备号失败\n");
            goto register_err;
        }
        VSER_MAJOR = MAJOR(dev);    //从设备号中提取主设备号
        VSER_MINOR = MINOR(dev);    //从设备号中提取次设备号
    }

    /******** 申请字符设备 *********/
    cdev_init(&vser_cdev,&vser_ops);
    vser_cdev.owner = THIS_MODULE;
    ret = cdev_add(&vser_cdev,dev,VSER_DEV_CNT);
    if(ret != 0)
    {
        printk("申请字符设备失败\n");
        goto cdev_err;
    }


    return 0;   //表示成功,直接结束函数并返回0

cdev_err:       /* 申请字符设备失败 */
    unregister_chrdev_region(dev,VSER_DEV_CNT); //释放设备号

register_err:   /* 设备号申请失败 */
    return ret;
    
}

static void __exit vser_exit(void)
{
    dev_t dev;  //用于存储设备号
    dev = MKDEV(VSER_MAJOR,VSER_MINOR); //组合成设备号 (VSER_MAJOR << 20) | VSER_MINOR

    cdev_del(&vser_cdev);                       //1.从内核散列表中删除字符设备
    unregister_chrdev_region(dev,VSER_DEV_CNT); //2.释放设备号
}

/****** 加入到内核 *******/
module_init(vser_init);
module_exit(vser_exit);
/****** 模块说明 ********/
MODULE_LICENSE("GPL");  /*开元许可协议:GPL协议*/

main.c     (记得使用  gcc   编译   )

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
    if(argc != 2)
    {
        return -1;
    }

    int fd = open(argv[1],O_RDWR); //会调用内核的 struct file_operations 结构体中的 open 指针,指针指向 vser_open 函数
    if(fd < 0)
    {
        perror("打开设备失败:");
        return -2;
    }
    
    close(fd);

    return 0;
}

Makefile 

#动态编译内核驱动生成.ko文件的Makeifle

#自己的模块代码名
obj-m = vser.o	#就会生成一个 vser.ko 文件

#内核源代码路径
ifeq ($(ARCH),arm)
	KERNELDIR ?= /home/student/linux-5.4.31
else
	KERNELDIR ?= /lib/modules/${shell uname -r}/build
endif

#当前模块路径
PWD ?= $(shell pwd)

#编译源码生成 .ko 文件 make all
all:
	${MAKE} -C ${KERNELDIR} M=${PWD} modules
#伪代码之清除垃圾
clean:
	rm Module.* modules.* *.mod *.ko


1. 过程:  gcc  mian.c    (把main.c  编译成  a.out)

 


2.  sudo  rmmod  vser  (卸载之前加载的模块)


3. make  (编译  makefile  文件)


4. sudo insmod vser.ko   (加载 模块)


5.命令: lsmod  (查看是否加载)

 



6. dmesg  (查看内核打印的信息, 这个没有内核信息打印)


3.虚拟串口 

代码: 

main_read.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{
    if(argc != 2)
    {
        return -1;
    }

    int fd = open(argv[1],O_RDWR); //会调用内核的 struct file_operations 结构体中的 open 指针,指针指向 vser_open 函数
    if(fd < 0)
    {
        perror("打开设备失败:");
        return -2;
    }
    
    char buf[10];
    while(1)
    {
        memset(buf,0,sizeof(buf));
        read(fd,buf,10);
        printf("读取到:%s\n",buf);
        sleep(1);
    }

    close(fd);

    return 0;
}

 main_write.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{
    if(argc != 2)
    {
        return -1;
    }

    int fd = open(argv[1],O_RDWR); //会调用内核的 struct file_operations 结构体中的 open 指针,指针指向 vser_open 函数
    if(fd < 0)
    {
        perror("打开设备失败:");
        return -2;
    }
    
    char buf[10];
    while(1)
    {
        printf("请输入要写入的数据:");
        scanf("%s",buf);
        write(fd,buf,strlen(buf));
    }

    close(fd);

    return 0;
}

vser.c

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>   
#include <linux/cdev.h>
/***** 设备相关信息 ******/
static unsigned int VSER_MAJOR = 256;      //主设备号
static unsigned int VSER_MINOR = 0;        //次设备号
#define VSER_DEV_CNT   1        //个数为1个
#define VSER_DEV_NAME  "vser"   //设备名称

/**** 字符设备结构体 ******/
static struct cdev vser_cdev;   //字符设备结构体

/**** 虚拟串口 ******/
#include <linux/kfifo.h>
DEFINE_KFIFO(vser_fifo,char,32);    //创建一个名为 vser_fifo 的 char 类型 大小为 32 个的队列管道

/**** 设备操作相关函数 到时候就可以使用 open 打开该设备文件 *****/
int vser_open(struct inode *p_inode, struct file *p_file)
{
    printk("vser设备打开了\n");
    return 0;
}

int vser_close(struct inode *p_inode, struct file *p_file)
{
    printk("vser设备关闭了\n");
    return 0;
}

ssize_t vser_read(struct file *p_file, char __user *user_buf, size_t user_size, loff_t *ps)
{   
    int copied = 0;
    
    kfifo_to_user(&vser_fifo,user_buf,user_size,&copied); //将内核队列管道数据拷贝到用户空间

    return copied;
}

ssize_t vser_write(struct file *p_file, const char __user *user_buf, size_t user_size, loff_t *ps)
{
    int copied = 0;

    kfifo_from_user(&vser_fifo,user_buf,user_size,&copied);//将用户空间数据拷贝到内核队列管道中

    return copied;
}

/**** 设备操作集 *****/
static struct file_operations vser_ops = {  //设备操作集结构体
    .owner = THIS_MODULE,
    .release = vser_close,
    .open = vser_open,
    .read = vser_read,
    .write = vser_write,
};


/***** 字符设备注册步骤 ***************************************************
 *        初始化函数
 * 1.将主设备和次设备组合成设备号     MKDEV(主设备号,次设备号)
 * 2.将该设备号注册到内核           register_chrdev_region(设备号,设备个数,设备名称) 或 
 * 3.初始化字符设备                cdev_init(字符设备结构体地址,操作集结构体地址)
 * 4.将字符设备添加到内核散列表map中  cdev_add(字符设备结构体地址,设备号,设备个数)
 *        注销函数
 * 1.从内核散链表map中删除字符设备    cdev_del(字符设备结构体地址)
 * 2.将申请的设备号注销              unregister_chrdev_region(设备号,设备个数) 
 * 
 * 跟文件系统下创建 设备文件 cat /proc/devices  
 * 查看到设备信息  
            Character devices:
            1 mem
            256 vser   我们自己申请的设备信息
            4 /dev/vc/0
            4 tty
            4 ttyS
            5 /dev/tty
            5 /dev/console  
 * ***********************************************************************/

static int __init vser_init(void)
{
    int ret;    //用于获取函数返回值,并该函数是否执行成功

    /*********  申请设备号 **************/
    dev_t dev;  //用于存储设备号
    dev = MKDEV(VSER_MAJOR,VSER_MINOR); //组合成设备号 (VSER_MAJOR << 20) | VSER_MINOR

    ret = register_chrdev_region(dev,VSER_DEV_CNT,VSER_DEV_NAME);
    if(ret != 0)
    {
        printk("静态申请设备号失败\n");

        ret = alloc_chrdev_region(&dev,VSER_MINOR,VSER_DEV_CNT,VSER_DEV_NAME);
        if(ret != 0)
        {
            printk("动态申请设备号失败\n");
            goto register_err;
        }
        VSER_MAJOR = MAJOR(dev);    //从设备号中提取主设备号
        VSER_MINOR = MINOR(dev);    //从设备号中提取次设备号
    }

    /******** 申请字符设备 *********/
    cdev_init(&vser_cdev,&vser_ops);
    vser_cdev.owner = THIS_MODULE;
    ret = cdev_add(&vser_cdev,dev,VSER_DEV_CNT);
    if(ret != 0)
    {
        printk("申请字符设备失败\n");
        goto cdev_err;
    }


    return 0;   //表示成功,直接结束函数并返回0

cdev_err:       /* 申请字符设备失败 */
    unregister_chrdev_region(dev,VSER_DEV_CNT); //释放设备号

register_err:   /* 设备号申请失败 */
    return ret;
    
}

static void __exit vser_exit(void)
{
    dev_t dev;  //用于存储设备号
    dev = MKDEV(VSER_MAJOR,VSER_MINOR); //组合成设备号 (VSER_MAJOR << 20) | VSER_MINOR

    cdev_del(&vser_cdev);                       //1.从内核散列表中删除字符设备
    unregister_chrdev_region(dev,VSER_DEV_CNT); //2.释放设备号
}

/****** 加入到内核 *******/
module_init(vser_init);
module_exit(vser_exit);
/****** 模块说明 ********/
MODULE_LICENSE("GPL");  /*开元许可协议:GPL协议*/

Makefile

#动态编译内核驱动生成.ko文件的Makeifle

#自己的模块代码名
obj-m = vser.o	#就会生成一个 vser.ko 文件

#内核源代码路径
ifeq ($(ARCH),arm)
	KERNELDIR ?= /home/student/linux-5.4.31
else
	KERNELDIR ?= /lib/modules/${shell uname -r}/build
endif

#当前模块路径
PWD ?= $(shell pwd)

#编译源码生成 .ko 文件 make all
all:
	${MAKE} -C ${KERNELDIR} M=${PWD} modules
#伪代码之清除垃圾
clean:
	rm Module.* modules.* *.mod *.ko


1. 编译所有的  .c 文件

gcc main_write.c -o write    (写文件)

gcc main_read.c -o  read    (读文件)

make    (编译  vser.ko 文件)


2. 命令: sudo rmmod  vser   (卸载之前加载的模块)


3. 命令:  sudo vser.ko     (加载现在的模块)


4. 命令: lsmod   (查看模块是否加载)


5. 命令:  sudo ./read   /dev/vser0    (运行读文件     后面的/dev/vser0   是之前创建的字符设备)

              sudo ./write  /dev/vser0     (运行写文件    后面的/dev/vser0   是之前创建的字符设备)


6. 命令 : dmesg  (查看内核信息)




4. 一个驱动支持多个设备

代码: 

vser.c

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>   
#include <linux/cdev.h>
/***** 设备相关信息 ******/
static unsigned int VSER_MAJOR = 256;      //主设备号
static unsigned int VSER_MINOR = 0;        //次设备号
#define VSER_DEV_CNT   2        //个数为1个
#define VSER_DEV_NAME  "vser"   //设备名称

// mknod /dev/vser0 c 256 0 
// mknod /dev/vser1 c 256 1

/**** 字符设备结构体 ******/
static struct cdev vser_cdev;   //字符设备结构体

/**** 虚拟串口 ******/
#include <linux/kfifo.h>
DEFINE_KFIFO(vser_fifo0,char,32);    //创建一个名为 vser_fifo0 的 char 类型 大小为 32 个的队列管道
DEFINE_KFIFO(vser_fifo1,char,32);    //创建一个名为 vser_fifo1 的 char 类型 大小为 32 个的队列管道
/**** 设备操作相关函数 到时候就可以使用 open 打开该设备文件 *****/
int vser_open(struct inode *p_inode, struct file *p_file)
{
    int minor = MINOR(p_inode->i_rdev); //获取设备的次设备号

    printk("vser设备打开了\n");
    printk("当前的次设备号;%d\n",minor);
    switch (minor)
    {
    case 0:
            p_file->private_data = &vser_fifo0;
            
        break;
    case 1:
            p_file->private_data = &vser_fifo1;
        break;
    }

    printk("操作的FIFO = %p\n",p_file->private_data);
    return 0;
}

int vser_close(struct inode *p_inode, struct file *p_file)
{
    printk("vser设备关闭了\n");
    return 0;
}

ssize_t vser_read(struct file *p_file, char __user *user_buf, size_t user_size, loff_t *ps)
{   
    int copied = 0;
    struct kfifo *fifo = p_file->private_data;

    kfifo_to_user(fifo,user_buf,user_size,&copied); //将内核队列管道数据拷贝到用户空间

    return copied;
}

ssize_t vser_write(struct file *p_file, const char __user *user_buf, size_t user_size, loff_t *ps)
{
    int copied = 0;
    struct kfifo *fifo = p_file->private_data;
    printk("操作的FIFO = %p\n",p_file->private_data);
    kfifo_from_user(fifo,user_buf,user_size,&copied);//将用户空间数据拷贝到内核队列管道中

    return copied;
}

/**** 设备操作集 *****/
static struct file_operations vser_ops = {  //设备操作集结构体
    .owner = THIS_MODULE,
    .release = vser_close,
    .open = vser_open,
    .read = vser_read,
    .write = vser_write,
};


/***** 字符设备注册步骤 ***************************************************
 *        初始化函数
 * 1.将主设备和次设备组合成设备号     MKDEV(主设备号,次设备号)
 * 2.将该设备号注册到内核           register_chrdev_region(设备号,设备个数,设备名称) 或 
 * 3.初始化字符设备                cdev_init(字符设备结构体地址,操作集结构体地址)
 * 4.将字符设备添加到内核散列表map中  cdev_add(字符设备结构体地址,设备号,设备个数)
 *        注销函数
 * 1.从内核散链表map中删除字符设备    cdev_del(字符设备结构体地址)
 * 2.将申请的设备号注销              unregister_chrdev_region(设备号,设备个数) 
 * 
 * 跟文件系统下创建 设备文件 cat /proc/devices  
 * 查看到设备信息  
            Character devices:
            1 mem
            256 vser   我们自己申请的设备信息
            4 /dev/vc/0
            4 tty
            4 ttyS
            5 /dev/tty
            5 /dev/console  
 * ***********************************************************************/

static int __init vser_init(void)
{
    int ret;    //用于获取函数返回值,并该函数是否执行成功

    /*********  申请设备号 **************/
    dev_t dev;  //用于存储设备号
    dev = MKDEV(VSER_MAJOR,VSER_MINOR); //组合成设备号 (VSER_MAJOR << 20) | VSER_MINOR

    ret = register_chrdev_region(dev,VSER_DEV_CNT,VSER_DEV_NAME);
    if(ret != 0)
    {
        printk("静态申请设备号失败\n");

        ret = alloc_chrdev_region(&dev,VSER_MINOR,VSER_DEV_CNT,VSER_DEV_NAME);
        if(ret != 0)
        {
            printk("动态申请设备号失败\n");
            goto register_err;
        }
        VSER_MAJOR = MAJOR(dev);    //从设备号中提取主设备号
        VSER_MINOR = MINOR(dev);    //从设备号中提取次设备号
    }

    /******** 申请字符设备 *********/
    cdev_init(&vser_cdev,&vser_ops);
    vser_cdev.owner = THIS_MODULE;
    ret = cdev_add(&vser_cdev,dev,VSER_DEV_CNT);
    if(ret != 0)
    {
        printk("申请字符设备失败\n");
        goto cdev_err;
    }


    return 0;   //表示成功,直接结束函数并返回0

cdev_err:       /* 申请字符设备失败 */
    unregister_chrdev_region(dev,VSER_DEV_CNT); //释放设备号

register_err:   /* 设备号申请失败 */
    return ret;
    
}

static void __exit vser_exit(void)
{
    dev_t dev;  //用于存储设备号
    dev = MKDEV(VSER_MAJOR,VSER_MINOR); //组合成设备号 (VSER_MAJOR << 20) | VSER_MINOR

    cdev_del(&vser_cdev);                       //1.从内核散列表中删除字符设备
    unregister_chrdev_region(dev,VSER_DEV_CNT); //2.释放设备号
}

/****** 加入到内核 *******/
module_init(vser_init);
module_exit(vser_exit);
/****** 模块说明 ********/
MODULE_LICENSE("GPL");  /*开元许可协议:GPL协议*/

main_write.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{
    if(argc != 2)
    {
        return -1;
    }

    int fd = open(argv[1],O_RDWR); //会调用内核的 struct file_operations 结构体中的 open 指针,指针指向 vser_open 函数
    if(fd < 0)
    {
        perror("打开设备失败:");
        return -2;
    }
    
    char buf[10];
    while(1)
    {
        printf("请输入要写入的数据:");
        scanf("%s",buf);
        write(fd,buf,strlen(buf));
    }

    close(fd);

    return 0;
}

main_read.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{
    if(argc != 2)
    {
        return -1;
    }

    int fd = open(argv[1],O_RDWR); //会调用内核的 struct file_operations 结构体中的 open 指针,指针指向 vser_open 函数
    if(fd < 0)
    {
        perror("打开设备失败:");
        return -2;
    }
    
    char buf[10];
    while(1)
    {
        memset(buf,0,sizeof(buf));
        read(fd,buf,10);
        printf("读取到:%s\n",buf);
        sleep(1);
    }

    close(fd);

    return 0;
}

Makefile

#动态编译内核驱动生成.ko文件的Makeifle

#自己的模块代码名
obj-m = vser.o	#就会生成一个 vser.ko 文件

#内核源代码路径
ifeq ($(ARCH),arm)
	KERNELDIR ?= /home/student/linux-5.4.31
else
	KERNELDIR ?= /lib/modules/${shell uname -r}/build
endif

#当前模块路径
PWD ?= $(shell pwd)

#编译源码生成 .ko 文件 make all
all:
	${MAKE} -C ${KERNELDIR} M=${PWD} modules
#伪代码之清除垃圾
clean:
	rm Module.* modules.* *.mod *.ko


1. 命令: sudo /dev/vser*     (卸载之前创建的字符设备模块  )


2.  sudo mknod  /dev/vser1  c  256 0   ( 创建字符设备)

    sudo  mknod  /dev/vser2  c  256 1    (设备名称不同,  次设备号不同 ,  自己看 vser.c  里面的  switch   的数字 决定次设备号)


3.命令:  su   (进入超级用户)

 


4. 命令:sudo rmmod  vser


5.命令 : make


6. 命令: sudo insmod  vser.ko 


7. 命令: gcc main_read.c  -o read


8. 命令:  gcc main_write.c  -o  write


9. 命令:  echo "我是" >  /dev/vser1


10 . 命令: cat /dev/vser1

 

 



作业: 

 

 1.字符设备和块设备的区别不包括()
[A]字符设备按字节流进行访问,块设备按块大小进行访问

[B]字符设备只能处理可打印字符,块设备可以处理二进制数据

[C]多数字符设备不能随机访问,而块设备一定能随机访问

[D]字符设备通常没有页高速缓存,而块设备有


2.在3.14.25版本的内核中,主设备号占———位,次设备号占———位。

[A]8
[B] 16
[C] 12
[D]20


3.用于分配主次设备号的函数是()。
[A] register_chrdev_region
[B] MKDEV
[C]alloc_chrdev_region
[D] MAJOR


4、在字符设备驱动中,struct file_operations 结构中的函数指针成员不包含$,

[A] open
[B] close
[C] read
[D] show_fdinfo

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/154264.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

青铜到王者,9本最好的Python从入门到进阶的书

春节长假还有2周了&#xff0c;是时候囤一些书充充电了&#xff01;新的一年群里很多小伙伴开始想学Python&#xff0c; 无论是准备转行的&#xff0c;还是想多学一份技能提高职场竞争力的&#xff0c;都想选择Python赛道&#xff0c;下面给大家推荐一些非常不错的Python入门到…

【Flask框架】—— 30 Flask-RESTful

前后端分离和前后端不分离 前后端不分离 在前后端不分离的应用模式中&#xff0c;前端页面看到的效果都是由后端控制&#xff0c;由后端渲染页面或重定向&#xff0c;也就是后端需要控制前端的展示&#xff0c;前端与后端的耦合度很高。 这种应用模式比较适合纯网页应用&…

iclr 2022 Compositional attention: Disentangling search and retrieval

Mittal S, Raparthy S C, Rish I, et al. Compositional attention: Disentangling search and retrieval[J]. arXiv preprint arXiv:2110.09419, 2021. 目录Mittal S, Raparthy S C, Rish I, et al. Compositional attention: Disentangling search and retrieval[J]. arXiv p…

【Kettle报错】kettle7.0链接MySQL显示:No appropriate protocol

【Kettle7.0链接本地MySQL数据库报错】 1. 问题描述 报错问题&#xff1a;kettle7.0报错&#xff0c;提示No appropriate protocol (protocol is disabled or cipher suites are inappropriate) 错误原因&#xff1a; Caused by: javax.net.ssl.SSLHandshakeException: No a…

Vue实现路由(Vue-router,参数传递,编程式路由导航)

目录 路由是什么&#xff1f; 怎么实现路由 第一步 创建一个文件夹 router &#xff0c;里面创建一个index.js 内容是 第二步 在main.js中引入和应用 router 第三步 在Vue中 通过两个标签进行配置 跳转路由时的参数传递 query params query参数和 param参数的区别 编…

原型链和JSON

对象的封装、继承和多态 封装、继承和多态是面向对象编程的三大特征&#xff0c;在JavaScript中也可以使用这些特征来实现面向对象的的编程。 封装是指将对象的属性和方法封装在对象内部&#xff0c;只提供必要的接口给外部访问。封装可以让我们隐藏对象的实现细节&#xff0c;…

项目管理:制定项目进度计划的好处有哪些?

项目管理计划确定了项目执行、监控及结束项目的整个过程&#xff0c;在项目开始之前&#xff0c;如能制定清晰的计划&#xff0c;并让项目成员都了解项目的目标和自己的责任&#xff0c;会对项目的推进有很大的帮助。 制定项目进度计划的好处有哪些&#xff1f; 1、目标导向…

高级树结构之二叉查找树

文章目录一 二叉查找树简介二 创建和插入操作三 查找操作3.1 查找思路3.2 代码实现四 删除操作4.1 情况讨论4.2 代码实现五 完整代码5.1 二叉查找树的结构5.2 完整代码内容一 二叉查找树简介 二叉查找树【二叉搜索树或是二叉排序树】 左子树中所有结点的值&#xff0c;均小于其…

(十一)devops持续集成开发——jenkins流水线发布一个docker harbor仓库版的前端vue项目

前言 本节内容&#xff0c;我们使用jenkins的流水线功能发布一个docker harbor私服仓库版的前端vue项目&#xff0c;延续前一节的内容&#xff0c;这里需要我们事先安装好一个docker harbor仓库用来存放我们项目的镜像&#xff0c;前端项目依然是通过nginx基础镜像构建&#x…

关于时间复杂度什么是时间复杂度

文章目录简介常见的Big-Oh简介 精确的考虑程序运行时间会使得寸步难行&#xff0c;而且毫无意义&#xff0c;所以可以用一种“概量”的概念来衡量运行时间&#xff0c;称之为“时间复杂度”。 时间复杂度的定义&#xff1a; 在一个完全立项状态下的计算机中&#xff0c;我们定…

【Sql Server】数据库的表变量和临时表的区别,并通过变量表随机生成姓名

作者&#xff1a;小5聊 简介&#xff1a;一只喜欢全栈方向的程序员&#xff0c;欢迎咨询&#xff0c;尽绵薄之力答疑解惑 公众号&#xff1a;有趣小馆&#xff0c;一个有趣的关键词回复互动功能 效果 1、表变量 1.1、表变量基本信息 1&#xff09;表变量本质是一个变量 是SQ…

《啊哈算法》第一章典例+解析+代码

目录 一&#xff0c;计数排序 二&#xff0c;冒泡排序&#xff08;Bubble Sort&#xff09; 三&#xff0c;快速排序&#xff08;Quick Sort&#xff09; 四&#xff0c;桶排序&#xff08;Bucket Sort&#xff09; 五&#xff0c;小哼买书 从无到有掌握最基础的算法 多学…

react hooks 封装一个countDown 倒计时组件

开发技术 react , hooks , ts , taro 需求分析 需要一个可以按天&#xff0c;时&#xff0c;分和秒来进行倒计时的组件。 简单使用 注&#xff1a;主要逻辑请看 useCountDown import CountDown from /components/countDown; import { useEffect, useState } from react; i…

东宝商城项目(二)——flask-script模块、flask-migrate模块和项目日志配置

学习flask-script模块的使用 1、什么是flask-script flask-script是flask的一个扩展模块&#xff0c;Flask-Script的作用是可以通过命令行的形式来操作Flask。 2、安装flask-script pip install flask-script 3、flask-script的使用 例如有这样一种需求&#xff1a; 我们…

李彦宏开年定调“百度式创新”:反馈驱动,坚定技术

今天在百度热搜看到这么一条置顶话题&#xff0c;让我印象深刻&#xff1a;读懂中国经济的信心所在。 站在2023年起点&#xff0c;无论你是阳了、没阳&#xff0c;还是阳康了&#xff0c;之于个人、企业组织&#xff0c;都太需要信心和激励了。 点进去后是一篇来自《人民日报 …

小程序直播加速抢占电商流量先机

临近春节&#xff0c;到了购置年货的时候&#xff0c;相信有不少小伙伴被淘宝、拼多多、抖音等各大平台的直播卖货吸引。近年来&#xff0c;大家逐渐发现视频直播的影响力已经渗透到各行各业&#xff0c;通过直播带来的流量&#xff0c;不少商家赚得盆满钵满。视频直播这块流量…

Autosar MCAL-GPT配置及使用

文章目录前言GPTGptChannelConfigSetGptChannelIdGptChannelModeGptChannelTickFrequencyGptChannelTickValueMaxGptEnableWakeupGptNotificationGptChannelClkSrcRefGptAssignedHwUnitGptConfigurationOfOptApiServicesGptDeinitApiGptEnableDisableNotificationApiGptTimeEla…

系列33 Flow_Model

Introduction 在上一小节中讲到了Latent Variable Model&#xff08;LAM&#xff09;&#xff0c;VAE。其主要思想就是将隐变量扩充为高维连续的分布&#xff0c;来增强模型的表达能力。而LAM模型中的核心困难是计算不出来&#xff0c;因为&#xff0c;而的维度过高算不出来。而…

GAN Step By Step -- Step7 WGAN

GAN Step By Step 心血来潮 GSBS&#xff0c;顾名思义&#xff0c;我希望我自己能够一步一步的学习GAN。GAN 又名 生成对抗网络&#xff0c;是最近几年很热门的一种无监督算法&#xff0c;他能生成出非常逼真的照片&#xff0c;图像甚至视频。GAN是一个图像的全新的领域&#…

2022 OceanBase 年度报告|用技术让海量数据的管理和使用更简单!

尊敬的各位客户、合作伙伴和开发者&#xff1a; 从 2020 年 6 月 1 日 OceanBase 开启商业化至今&#xff0c;我们一起走过了 900 多天。 从 0.5 到 3.x&#xff0c;我们花了近十年时间&#xff0c;而从 3.x 到 4.x 只用了不到两年&#xff0c;这是 OceanBase 和客户、伙伴、…