精华内容
下载资源
问答
  • 以下设备中属于字符设备的是
    千次阅读
    2020-10-14 16:41:19

    一、linux设备驱动的分类

    1、字符设备—c
    应用程序和驱动程序之间进行数据读写的时候,数据是以“字节”为单位。数据交互的时候,是按照固定的顺序传输的;数据是实时传输的,是没有缓存的。字符设备是没有文件系统的。
    绝大部分设备驱动是字符设备:LED、BEEP、按键、键盘、触摸屏、摄像头、液晶屏、声卡、IIC、SPI、…
    应用程序:系统IO函数
    open("/dev/led_drv", O_RDWR)
    read()
    write()
    ioctl()
    mmap()
    close()

    2、块设备—b
    应用程序和驱动程序之间进行数据读写的时候,数据是以“块”为单位,1block=1024KB。块设备是有缓存的,块设备是有文件系统的。
    大容量的存储设备一般都是块设备:nand flash、eMMC、SD、U盘、硬盘、…

    #cat /proc/partitions 
    major minor  #blocks  name
    
     179        0    7634944 mmcblk0
     179        1      65536 mmcblk0p1
     179        2     772096 mmcblk0p2
     179        3     438272 mmcblk0p3
     179        4          1 mmcblk0p4
     179        5       8192 mmcblk0p5
     179        6      22528 mmcblk0p6
     179        7    6324224 mmcblk0p7
     179       16       4096 mmcblk0boot1
     179        8       4096 mmcblk0boot0
       8        0    1956864 sda
       8        1    1802240 sda1
    

    应用程序访问块设备

    [root@GEC6818 /]#ls /dev/sda* -l
    brw-rw-rw-    1 root     root        8,   0 Jan  1 00:11 /dev/sda  --->U盘
    brw-rw-rw-    1 root     root        8,   1 Jan  1 00:11 /dev/sda1 --->U盘的一个放数据的分区
    

    1)挂载—块设备是有文件系统的。

    [root@GEC6818 /]#mount -t vfat /dev/sda1 /data
    

    vfat ---->fat32

    2)像访问普通文件一样访问块设备的内容。
    标准IO函数:fopen()/fread()/fwrite()/fclose()

    3、网络设备
    网卡类的设备:有线网卡、无线网卡、…,网络设备是没有设备文件的。

    应用程序:
    socket套接字: IP + 端口号

     

    二、字符设备驱动的设计流程

    ------定义并初始化一个字符设备---------
    1、定义一个字符设备—>struct cdev
    2、定义并初始化字符设备的文件操作集—>struct file_operations
    3、给字符设备申请一个设备号—>设备号=主设备号<<20 + 次设备号
    4、初始化字符设备
    5、将字符设备加入内核

    -------自动生成设备文件---------
    6、创建class
    7、创建device,其中device是属于class的

    -------得到物理地址对应的虚拟地址-------
    8、申请物理内存区,申请SFR的地址区。SFR — Special Function Register: GPIOEOUT
    9、内存的动态映射,得到物理地址对应的虚拟地址
    10、访问虚拟地址

     

    三、定义一个字符设备

    1、描述字符设备的结构体–cdev

    #include <linux/cdev.h>
    struct cdev {
    	struct kobject kobj;
    	struct module *owner;
    	const struct file_operations *ops;
    	struct list_head list;
    	dev_t dev;
    	unsigned int count;
    };
    

    在linux内核中,使用cdev来描述一个字符设备,每个字符设备都有一个自己的cdev。设计字符设备首先定义一个cdev。

    例:

    struct cdev gec6818_led_cdev;
    

    2、cdev的成员
    struct kobject kobj; —>内核管理驱动的时候,使用的一个object
    struct module *owner; —>cdev是属于哪个module,一般写成THIS_MODULE
    const struct file_operations *ops; —>cdev的文件操作集
    struct list_head list; —>内核管理cdev的链表
    dev_t dev; —>设备号
    unsigned int count; —>次设备的数量

     

    四、定义并初始化一个文件操作集

    1、文件操作集

    struct file_operations {
    	struct module *owner;
    	...............................
    	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    	int (*mmap) (struct file *, struct vm_area_struct *);
    	int (*open) (struct inode *, struct file *);
    	int (*release) (struct inode *, struct file *);
    	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    	..............................
    }
    

    2、文件操作集的作用
    每个cdev都有一个文件操作集,文件操作集是驱动程序给应用程序提供的接口。应用程序open()会找到驱动程序的open(),驱动程序的open()可以用来访问硬件。

    3、例

    int gec6818_led_open(struct inode *inode, struct file *filp)
    {
    	
    	return 0;
    }
    ssize_t gec6818_led_read(struct file *filp, char __user *user_buf, size_t size, loff_t *off)
    {
    	
    }
    
    ssize_t gec6818_led_write(struct file *filp, const char __user *user_buf, size_t size, loff_t *off)
    {
    	
    }
    
    int gec6818_led_release(struct inode *inode, struct file *filp)
    {
    	
    	return 0;
    }
    
    static const struct file_operations gec6818_led_fops = {
    	.owner = THIS_MODULE,
    	.open = gec6818_led_open,
    	.read = gec6818_led_read,
    	.write = gec6818_led_write,
    	.release = gec6818_led_release,
    };
    

     

    五、给字符设备申请一个设备号—dev_t dev

    1、什么是设备号
    每个设备文件(字符设备 or 块设备)都已一个设备号,相当于设备文件ID。
    设备号有主设备号和次设备号组成的。

    设备号是一个32bits的无符号整型值。
    typedef __u32 __kernel_dev_t;
    typedef __kernel_dev_t dev_t;

    2、设备号运算的函数:
    1)由主设备号和次设备号生成设备号
    #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //MINORBITS=20

    2)由设备号得到主设备号和次设备号
    #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
    #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

    3、主设备号和次设备号的作用
    例:

    crw-rw----    1 root     root      204,  64 Jan  1  1970 ttySAC0  串口0
    crw-rw----    1 root     root      204,  65 Jan  1  1970 ttySAC1
    crw-rw----    1 root     root      204,  66 Jan  1  1970 ttySAC2
    crw-rw----    1 root     root      204,  67 Jan  1  1970 ttySAC3  串口3
    

    主设备号描述一个硬件设备的类型:如uart、IIC、摄像头、…
    次设备号描述这种硬件类型下的具体某个硬件

    4、如何申请设备号
    1)静态注册—>指定设备号,注册到内核中。如果内核已经使用该设备号,注册就不成功。

    /**
     * register_chrdev_region() - register a range of device numbers
     * @from: the first in the desired range of device numbers; must include the major number.
     * @count: the number of consecutive device numbers required
     * @name: the name of the device or driver.
     *
     * Return value is zero on success, a negative error code on failure.
     */
    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    
    
    

    参数说明:
    dev_t from —>注册的设备号;如果一次注册多个设备号,from就是注册设备号的开始值
    unsigned count —>次设备的数量
    const char *name ---->设备名称,但不是设备文件的名字。#cat /proc/devices
    返回值:
    成功返回0,失败返回复数错误码。

    例:

    crw-rw----    1 root     root      204,  64 Jan  1  1970 ttySAC0  串口0
    crw-rw----    1 root     root      204,  65 Jan  1  1970 ttySAC1
    crw-rw----    1 root     root      204,  66 Jan  1  1970 ttySAC2
    crw-rw----    1 root     root      204,  67 Jan  1  1970 ttySAC3  串口3
    
    register_chrdev_region(MKDEV(204,64), 4, "ttySAC") //ttySAC --->设备名称
                                                       // /dev/ttySAC0 --->设备文件
    

    2)动态分配—>内核自动分配空闲的设备号

    /**
     * alloc_chrdev_region() - register a range of char device numbers
     * @dev: output parameter for first assigned number
     * @baseminor: first of the requested range of minor numbers
     * @count: the number of minor numbers required
     * @name: the name of the associated device or driver
     * Allocates a range of char device numbers.  The major number will be
     * chosen dynamically, and returned (along with the first minor number)
     * in @dev.  Returns zero or a negative error code.
     */
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
    			const char *name)
    

    参数说明:

    dev_t *dev —>分配后的设备号
    unsigned baseminor —>次设备号的开始值
    unsigned count —>次设备的数量
    const char *name ---->设备名称,但不是设备文件的名字。#cat /proc/devices
    返回值:
    成功返回0,失败返回复数错误码。

    3)设备号的注销

    /**
     * unregister_chrdev_region() - return a range of device numbers
     * @from: the first in the range of numbers to unregister
     * @count: the number of device numbers to unregister
     *
     * This function will unregister a range of @count device numbers,
     * starting with @from.  The caller should normally be the one who
     * allocated those numbers in the first place...
     */
    void unregister_chrdev_region(dev_t from, unsigned count)
    

    参数说明:
    dev_t from —>注册的设备号;如果一次注册多个设备号,from就是注册设备号的开始值
    unsigned count —>次设备的数量

     

    六、初始化字符设备

    /**
     * cdev_init() - initialize a cdev structure
     * @cdev: the structure to initialize
     * @fops: the file_operations for this device
     *
     * Initializes @cdev, remembering @fops, making it ready to add to the
     * system with cdev_add().
     */
    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    

    思考:
    static struct cdev gec6818_led_cdev; //有内存
    cdev_init(&gec6818_led_cdev, const struct file_operations *fops);

    或:
    static struct cdev *gec6818_led_cdev; //没有内存
    cdev_init(gec6818_led_cdev, const struct file_operations *fops);//segment fault

    ok:
    static struct cdev *gec6818_led_cdev;
    gec6818_led_cdev = (struct cdev *)kmalloc(sizeof(struct cdev), GFP_KERNEL)
    if(gec6818_led_cdev == NULL){

    }
    cdev_init(gec6818_led_cdev, const struct file_operations *fops);//segment fault

     

    七、将字符设备加入内核

    /**
     * cdev_add() - add a char device to the system
     * @p: the cdev structure for the device
     * @dev: the first device number for which this device is responsible
     * @count: the number of consecutive minor numbers corresponding to this  device
     * cdev_add() adds the device represented by @p to the system, making it
     * live immediately.  A negative error code is returned on failure.
     */
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    

    参数:
    struct cdev *p —>定义初始化好的字符设备
    dev_t dev —>设备号
    unsigned count —>次设备的数量
    返回值:
    A negative error code is returned on failure.

    /**
     * cdev_del() - remove a cdev from the system
     * @p: the cdev structure to be removed
     *
     * cdev_del() removes @p from the system, possibly freeing the structure
     * itself.
     */
    void cdev_del(struct cdev *p)
    

     

    八、创建class

    创建class和device的目的是在安装的驱动的时候,可以自动生成设备文件,在卸载驱动的时候,可以自动的删除设备文件。
    如果不自动生成设备文件:也可以手动创建:
    #mkmod c /dev/led_drv 主设备号 次设备号

    创建的class生成在:/sys/class/

    1.创建class

    #include <linux/device.h>
    struct class *class_create(struct module *owner, const char *name)
    

    参数说明:
    struct module *owner —>创建的class属于哪个module,一般为THIS_MODULE。
    const char *name —>自定义的class的名字
    返回值:
    得到的class

    2.class的删除

    void class_destroy(struct class *cls);
    

     

    九、创建device

    device是输于class的,当驱动程序有了class和device以后,内核使用mdev这个工具,根据class和device创建该驱动的设备文件。

    创建的device怎么查看:/sys/class/***/

    /**
     * device_create - creates a device and registers it with sysfs
     * @class: pointer to the struct class that this device should be registered to
     * @parent: pointer to the parent struct device of this new device, if any
     * @devt: the dev_t for the char device to be added
     * @drvdata: the data to be added to the device for callbacks
     * @fmt: string for the device's name
     
     * This function can be used by char device classes.  A struct device
     * will be created in sysfs, registered to the specified class.
     * A "dev" file will be created, showing the dev_t for the device, if
     * the dev_t is not 0,0.
     * If a pointer to a parent struct device is passed in, the newly created
     * struct device will be a child of that device in sysfs.
     * The pointer to the struct device will be returned from the call.
     * Any further sysfs files that might be required can be created using this
     * pointer.
     *
     * Returns &struct device pointer on success, or ERR_PTR() on error.
     *
     * Note: the struct class passed to this function must have previously
     * been created with a call to class_create().
     */
    struct device *device_create(struct class *class, struct device *parent,
    			     dev_t devt, void *drvdata, const char *fmt, ...)
    

    参数说明:
    struct class *class —>device属于哪个class
    struct device *parent —>device的父设备,一般为NULL
    dev_t devt —>设备号
    void *drvdata —>驱动的data,一般为NULL
    const char *fmt —>设备文件的名字
    返回值:
    struct device * —>创建好的device

    2.删除device

    /**
     * device_destroy - removes a device that was created with device_create()
     * @class: pointer to the struct class that this device was registered with
     * @devt: the dev_t of the device that was previously registered
     *
     * This call unregisters and cleans up a device that was created with a
     * call to device_create().
     */
    void device_destroy(struct class *class, dev_t devt)
    

     

    十、申请物理内存区

    回忆:
    裸机控制硬件的流程:
    分析原理图–>找到控制硬件的GPIO–>找GPIO的寄存器—>分析寄存器—>理解寄存器的控制顺序—>通过寄存器的地址来访问该寄存器

    注意:裸机使用的是物理地址,所以直接使用CPU手册查到的地址可以编程。
    linux驱动使用的虚拟地址,不能直接使用物理地址。想办法,如果通过CPU手册查到的物理地址找到其对应虚拟地址???

    一般分成两个过程:
    申请物理地址区作为一个资源----->将物理内存区做内存的动态映射,得到虚拟地址。

    注意:
    资源—有限的,一旦一个物理内存区已经申请了,后面就不能再次申请。

    1.申请物理内存区作为资源

    struct resource *  request_mem_region(resource_size_t start,resource_size_t n,const char *name)
    

    参数说明:
    resource_size_t start —>物理内存区的开始地址
    resource_size_t n —>物理内存区的大小
    const char *name —>自定义的物理内存区的名字
    返回值:
    struct resource * —>物理内存区作为了资源

    思考:
    LED驱动,申请哪个物理内存区???

    D8–>GPIOC17,D9–>GPIOC8,D10–>GPIOC7,D11–>GPIOC12

    start address —>0xC001C000
    address size —> 结束地址:0xC001CFFF,大小:0x1000
    name —>“GPIOC_MEM”

    2.释放申请的物理内存区

    void release_mem_region(resource_size_t start, resource_size_t n)
    

     

    十一、io内存动态映射,得到虚拟地址

    1.IO内存动态映射
    将一段物理地址内存区映射成一段虚拟地址内存区

    #include <linux/io.h>
    void __iomem *ioremap(phys_addr_t offset, unsigned long size)
    

    参数说明:
    phys_addr_t offset —>要映射的物理内存区开始地址
    unsigned long size —>物理内存区的大小
    返回值:
    void __iomem * —>映射后,虚拟地址内存区的首地址

    2、解除IO内存动态映射

     void iounmap(void __iomem *addr)
    

     

    十二、使用虚拟地址

    1、得到虚拟地址

    gpioc_base_va = ioremap(phys_addr_t offset, unsigned long size)
    if(gpioc_base_va == NULL){
    	printk("ioremap error\n");
    	release_mem_region(0xC001C000, 0x1000);
    	device_destroy(leds_class, led_num);
    	class_destroy(leds_class);
    	cdev_del(&gec6818_led_cdev);
    	unregister_chrdev_region(led_num, 1);
    	
    	return -EBUSY;
    }
    //得到每个寄存器的虚拟地址
    gpiocout_va = gpioc_base_va + 0x00;
    gpiocoutenb_va = gpioc_base_va + 0x04;
    gpiocaltfn0_va = gpioc_base_va + 0x20;
    gpiocaltfn1_va = gpioc_base_va + 0x24;
    gpiocpad_va = gpioc_base_va + 0x18;
    

    2、虚拟地址的类型:void __iomem

    3、访问虚拟地址的方法:与访问物理地址的方法一样

    //10.访问虚拟地址
    //10.1 GPIOC7,8.12,17 --->function1,作为普通的GPIO
    *(unsigned int *)gpiocaltfn0_va &=~((3<<14)|(3<<16)|(3<<24));
    *(unsigned int *)gpiocaltfn1_va &=~(3<<2);
    *(unsigned int *)gpiocaltfn0_va |= ((1<<14)|(1<<16)|(1<<24));
    *(unsigned int *)gpiocaltfn1_va |= (1<<2);
    //10.2 GPIOC7,8.12,17 --->设置为输出
    *(unsigned int *)gpiocoutenb_va |= ((1<<7)|(1<<8)|(1<<12)|(1<<17));
    //10.3 GPIOC7,8.12,17 --->设置为输出高电平,D8~D11 off
    *(unsigned int *)gpiocout_va |= ((1<<7)|(1<<8)|(1<<12)|(1<<17));
    

    4、虚拟地址的访问方法:使用内核提供的函数

    u32 readl(const volatile void __iomem *addr)
    void writel(u32 b, volatile void __iomem *addr)
    

    或者:

    void __raw_writel(u32 b, volatile void __iomem *addr)
    u32 __raw_readl(const volatile void __iomem *addr)
    

     

    十三、常见的错误码

    #include <linux/errno.h>
    #define	EPERM		 1	/* Operation not permitted */
    #define	ENOENT		 2	/* No such file or directory */
    #define	ESRCH		 3	/* No such process */
    #define	EINTR		 4	/* Interrupted system call */
    #define	EIO		 	 5	/* I/O error */
    #define	ENXIO		 6	/* No such device or address */
    #define	E2BIG		 7	/* Argument list too long */
    #define	ENOEXEC		 8	/* Exec format error */
    #define	EBADF		 9	/* Bad file number */
    #define	ECHILD		10	/* No child processes */
    #define	EAGAIN		11	/* Try again */
    #define	ENOMEM		12	/* Out of memory */
    #define	EACCES		13	/* Permission denied */
    #define	EFAULT		14	/* Bad address */
    #define	ENOTBLK		15	/* Block device required */
    #define	EBUSY		16	/* Device or resource busy */
    #define	EEXIST		17	/* File exists */
    #define	EXDEV		18	/* Cross-device link */
    #define	ENODEV		19	/* No such device */
    #define	ENOTDIR		20	/* Not a directory */
    #define	EISDIR		21	/* Is a directory */
    #define	EINVAL		22	/* Invalid argument */
    #define	ENFILE		23	/* File table overflow */
    #define	EMFILE		24	/* Too many open files */
    #define	ENOTTY		25	/* Not a typewriter */
    #define	ETXTBSY		26	/* Text file busy */
    #define	EFBIG		27	/* File too large */
    #define	ENOSPC		28	/* No space left on device */
    #define	ESPIPE		29	/* Illegal seek */
    #define	EROFS		30	/* Read-only file system */
    #define	EMLINK		31	/* Too many links */
    #define	EPIPE		32	/* Broken pipe */
    #define	EDOM		33	/* Math argument out of domain of func */
    #define	ERANGE		34	/* Math result not representable */
    

    十四、用户空间和内核空间交互数据

    1.从用户空间获取数据

    #include <linux/uaccess.h>
    unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
    

    放在驱动程序的write().

    2.将数据拷贝给用户

    unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
    

    放在驱动程序的read()

     

    十五、驱动程序的调试

    1、应用程序—使用系统IO的函数
    arm-linux-gcc -o test test.c

    $ file test
    test: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 3.2.0, not stripped

    2、驱动程序—使用字符设备驱动模型

    1)查看主设备号和设备名称

    root@GEC6818 /test]#cat /proc/devices
    Character devices:
     
    100 led_device
    

    2)查看设备文件

    [root@GEC6818 /test]#ls /dev/led_drv -l
    crw-rw----    1 root     root      100,   0 Jan  1 00:04 /dev/led_drv
    

    3)查看申请的内存

    [root@GEC6818 /test]#cat /proc/iomem
    
    c001c000-c001cfff : GPIOC_MEM
    

    4)查看class和device

    [root@GEC6818 /test]#ls /sys/class/gec210_leds/
    led_drv
    

     

    十六、安装多个驱动出错解决

    1、出错

    [root@GEC6818 /test]#lsmod
    led_drv 2881 0 - Live 0xbf025000 (O)
    
    [root@GEC6818 /test]#insmod beep_drv.ko
    [ 1643.707000] request memory error
    insmod: can't insert 'beep_drv.ko': Device or resource busy
    

    2、原因分析
    当LED驱动安装成功后,再安装beep的驱动会出现问题:

    [ 1643.707000] request memory error
    insmod: can't insert 'beep_drv.ko': Device or resource busy
    

    1)beep_drv.c

    //8申请物理内存区
    beeps_res = request_mem_region(0xC001C000, 0x1000,"GPIOC_MEM");
    if(beeps_res == NULL){
    	printk("request memory error\n");
    	device_destroy(beeps_class, beep_num);
    	class_destroy(beeps_class);
    	cdev_del(&gec6818_beep_cdev);
    	unregister_chrdev_region(beep_num, 1);
    	
    	return -EBUSY;
    }
    

    2)led_drv.c

    //8申请物理内存区
    leds_res = request_mem_region(0xC001C000, 0x1000,"GPIOC_MEM");
    if(leds_res == NULL){
    	printk("request memory error\n");
    	ret = -EBUSY;
    	goto request_mem_error;
    }
    

    两个驱动申请的物理内存区是一个:0xC001C000, 0x1000。物理内存区作为一个resource,只能申请一次。

    3、解决
    不申请物理内存区资源:

    附录

    代码示例:led_drv.c

    更多相关内容
  • 主要介绍了Linux 字符设备驱动框架详细介绍的相关资料,字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,这里提供简单的实例,需要的朋友可以参考下
  • 字符设备

    2021-02-02 16:00:29
    字符设备0.设备驱动的分类1./dev目录下设备的区分2.字符设备驱动的设计过程3.定义一个字符设备4.定义并初始化一个文件操作集5.给字符设备申请设备号6.初始化设备7.编写应用程序8.使用动态分配设备号9.驱动程序与应用...

    0.设备驱动的分类

    设备驱动分为三大类:字符设备、块设备、网络设备。
    1.字符设备:

    该设备对数据的处理按照字节流的形式进行的,支持顺序访问(是有时间的概念),也可以支持随机访问。
    典型的字符设备:串口、键盘、触摸屏、摄像头、I2C、SPI、声卡、帧缓冲设备…
    顺序访问的设备:串口、键盘、触摸屏
    随机访问的设备:帧缓冲设备

    应用程序,能够使用系统IO函数来就行访问:open、write、read、lseek、close…

    crw--w---- 1 root tty     4,  58 Jan  1  1970 tty58
    crw--w---- 1 root tty     4,  59 Jan  1  1970 tty59
    crw--w---- 1 root tty     4,   6 Jan  1  1970 tty6
    crw--w---- 1 root tty     4,  60 Jan  1  1970 tty60
    crw--w---- 1 root tty     4,  61 Jan  1  1970 tty61
    crw--w---- 1 root tty     4,  62 Jan  1  1970 tty62
    crw--w---- 1 root tty     4,  63 Jan  1  1970 tty63
    crw--w---- 1 root tty     4,   7 Jan  1  1970 tty7
    crw--w---- 1 root tty     4,   8 Jan  1  1970 tty8
    crw--w---- 1 root tty     4,   9 Jan  1  1970 tty9
    crw------- 1 root tty   207,  16 Aug 14 12:44 ttymxc0
    crw------- 1 root root  207,  18 Jan  1  1970 ttymxc2
    crw------- 1 root root   10,  55 Jan  1  1970 ubi_ctrl
    
    

    设备号方便操作系统进行管理,就例如进程,方便给系统管理,就有一个进程号,即pid,使用ps命令可以知道每个进程的id号;
    线程,方便给系统管理,就有一个线程号,即tid,创建线程返回线程id;
    就像每个人都会有一个名字,但是名字容易出现同名,这个时候可以使用身份证号码进行区分。

    2.块设备:
    该设备的处理按照若干个块来进行的。一个块的固定大小512字节、4096字节。这类设备支持随机访问,这种以块和随机访问能够提高数据存储效率。
    块设备往往是面向于存储类的设备:nand flash、SD卡、U盘、eMMC、硬盘…。

    块设备在/dev目录详细表现形式:

    brw-rw---- 1 root disk    1,   0 Jan  1  1970 ram0
    brw-rw---- 1 root disk    1,   1 Jan  1  1970 ram1
    brw-rw---- 1 root disk    1,  10 Jan  1  1970 ram10
    brw-rw---- 1 root disk    1,  11 Jan  1  1970 ram11
    brw-rw---- 1 root disk    1,  12 Jan  1  1970 ram12
    brw-rw---- 1 root disk    1,  13 Jan  1  1970 ram13
    brw-rw---- 1 root disk    1,  14 Jan  1  1970 ram14
    brw-rw---- 1 root disk    1,  15 Jan  1  1970 ram15
    brw-rw---- 1 root disk    1,   2 Jan  1  1970 ram2
    brw-rw---- 1 root disk    1,   3 Jan  1  1970 ram3
    brw-rw---- 1 root disk    1,   4 Jan  1  1970 ram4
    brw-rw---- 1 root disk    1,   5 Jan  1  1970 ram5
    brw-rw---- 1 root disk    1,   6 Jan  1  1970 ram6
    brw-rw---- 1 root disk    1,   7 Jan  1  1970 ram7
    brw-rw---- 1 root disk    1,   8 Jan  1  1970 ram8
    brw-rw---- 1 root disk    1,   9 Jan  1  1970 ram9
    
    

    3.网络设备:
    网络设备是比较特殊的,在/dev没有设备文件,它就是专门针对网络设备的一类驱动,其主要作用是进行网络的数据收发。
    网络类设备:有线网卡、无线WiFi网卡(RT3070)、无线GPRS网卡、无线4G网卡…
    应用程序:
    socket套接字

    1./dev目录下设备的区分

    1.文件类型

    • c:字符设备
    • b:块设备
    • d:目录
    • l:符号链接

    在/dev目录主要集中了字符设备和块设备,字符设备以c作为开头,块设备以b作为开头,详细如下:

    crw-rw----    1 root     root       29,   0 Jan  1  1970 fb0
    brw-rw----    1 root     root      179,   1 Jan  1  1970 mmcblk0p1
    

    2.设备号
    其实设备号等同于身份证号码,方便系统进行管理。设备文件跟普通文件区别在于比普通文件多出了两个数字,这两个数字分别是主设备号和次设备号。

    普通文件:

    root@ATK-IMX6U:~# ls -l 1.mp3
    -rw-r--r--    1 root     root       8941241 Jan  1  2015 1.mp3
    

    设备文件:

    root@ATK-IMX6U:~#  /dev]#ls -l fb0
    crw-rw----    1 root     root       29,   0 Jan  1  1970 fb0
    

    观察fb0设备文件,多出的数字为“29,0”,详细如下:

    • 29:主设备号
    • 0:次设备号

    主设备号: 区分某一类的设备。
    ttySAC这是串口设备,主设备号为204;
    mmcblk0这是电子硬盘属于块设备,主设备号为179。

    次设备号: 用于区分同一类设备的不同个体或不同分区。
    串口设备:串口0串口3,则使用次设备号6467进行区分。
    电子硬盘设备:分区1分区7,则使用次设备号17进行区分。

    身份证: 前六位数字,就代表是某个地区,用于区分不同地方的人。
    后面的其他数字是具体到某个人。

    2.字符设备驱动的设计过程

    1. 定义一个字符设备,struct cdev

    2. 申请设备号
      1)静态注册
      MKDEV
      register_chrdev_region

      2)动态注册
      alloc_chrdev_region

    3. 定义file_operations,初始化打开、关闭、读、写等函数接口。

    4. cdev初始化
      .cdev_init

    5. 将cdev加入到内核
      .cdev_add

    6.创建设备文件
    .手动创建,使用mknod命令去/dev目录进行创建
    .自动创建
    1)class_create
    2)device_create

    3.定义一个字符设备

    1. 描述字符设备的结构体——cdev
      #include <linux/cdev.h>
    
    struct cdev {
    	struct kobject kobj;
    	struct module *owner;
    	const struct file_operations *ops;
    	struct list_head list;
    	dev_t dev;
    	unsigned int count;
    };
    

    在linux内核当中,使用cdev来描述一个设备,每个字符设备都有自己一个cdev,设计字符设备的时候,首先定义一个cdev。
    例如:

    static struct cdev imx6ull_led_cdev;
    

    2.cdev的成员

    • struct kobject kobj,内核管理驱动的时候,使用到的一个object,自动调用。
    • struct module owner,cdev是属于哪一个module,默认初始化为THIS_MODULE,指向当前模块,类似于C++ this指针,这个成员可阻止该模块还在被使用时被卸载。【
    • const struct file_operations ops,字符设备文件操作集。【
    • struct list_head list,内核管理字符设备的链表,自动调用。
    • dev_t dev,设备号。【 】
    • unsigned int count,次设备的数量。【*】

    4.定义并初始化一个文件操作集

    1.文件操作集

    #include <linux/fs.h>
    
    struct file_operations {
    	struct module *owner;
    ...........
    	loff_t (*llseek) (struct file *, loff_t, int);
    	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
         ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
         long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
         int (*open) (struct inode *, struct file *);
         int (*release) (struct inode *, struct file *);
    	...........
    };
    

    2.文件操作集的作用
    每个字符设备都必须有一个文件操作集,文件操作集是驱动程序给应用程序访问硬件的一个接口,应用层与驱动程序函数接口的对应关系如下:

    应用层 驱动层
    open open
    close release
    write write
    read read
    lseek llseek
    ioctl unlocked_ioctl

    例子:

     static int led_release(struct inode *inode, struct file *filp)
     {
     return 0;
     }
    
     /* 设备操作函数 */
     static struct file_operations newchrled_fops = {
     .owner = THIS_MODULE,
     .open = led_open,
     .read = led_read,
     .write = led_write,
     .release = led_release,
     };
    

    5.给字符设备申请设备号

    (一)详细了解设备号

    每个设备文件(字符设备 or 块设备)都有一个32位的设备号,相当于设备文件ID信息。
    每个设备号 = 主设备号 + 次设备号

    typedef __u32 __kernel_dev_t;
    typedef __kernel_dev_t		dev_t;
    

    (二)设备号的运算函数
    1.MKDEV

    #define MINORBITS	20
    #define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))
    

    设备号 = 高12位的主设备号[31:20] + 低20位的次设备号[19:0] 组成。

    通过设备号来获取主设备号或次设备号

    #define MINORMASK	((1U << MINORBITS) - 1)
    
    
    //获取主设备号
    #define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
    
    
    //获取次设备号
    #define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
    

    (三)如何申请设备号

    1.静态注册,register_chrdev_region

    #include <linux/fs.h>
    
    /**
     * register_chrdev_region() - register a range of device numbers
     * @from: the first in the desired range of device numbers; must include
     *        the major number.
     * @count: the number of consecutive device numbers required
     * @name: the name of the device or driver.
     *
     * Return value is zero on success, a negative error code on failure.
     */
    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    
    

    参数说明:
    from,注册的设备号,如果一次要注册多个设备,from就是注册设备号的开始值。
    count,次设备的数量
    name,设备名称,但是该名称不是/dev目录下设备文件的名字,而是在/proc/devices文件当中。

    返回值:
    成功,返回0;
    失败,返回负数的错误码。

    补充说明:
    将该设备号注册到内核当中。如果该设备号在内核已经使用了,注册失败。参考内核源代码的设备号设置的文档\Documentation\devices.txt。

    ......................................................
    
     29 char	Universal frame buffer
    		  0 = /dev/fb0		First frame buffer
    		  1 = /dev/fb1		Second frame buffer
    		    ...
    		 31 = /dev/fb31		32nd frame buffer
    		 
    		 
     31 block	ROM/flash memory card
    		  0 = /dev/rom0		First ROM card (rw)
    		      ...
    		  7 = /dev/rom7		Eighth ROM card (rw)
    		  8 = /dev/rrom0	First ROM card (ro)
    		    ...
    		 15 = /dev/rrom7	Eighth ROM card (ro)
    		 16 = /dev/flash0	First flash memory card (rw)
    		    ...
    		 23 = /dev/flash7	Eighth flash memory card (rw)
    		 24 = /dev/rflash0	First flash memory card (ro)
    		    ...
    		 31 = /dev/rflash7	Eighth flash memory card (ro)
    
    		The read-write (rw) devices support back-caching
    		written data in RAM, as well as writing to flash RAM
    		devices.  The read-only devices (ro) support reading
    		only.
    
      234-239		UNASSIGNED
      240-254 char	LOCAL/EXPERIMENTAL USE
    		............................
    

    静态注册常用的主设备号可以从234开始进行尝试,在当前内核当中,没有分配登记的从234-239。谨记,不同的内核版本,主设备号占用的数值都会有所出入。

    2.动态分配,就是由内核自动分配空闲的设备号

    #include <linux/fs.h>
    
    /**
     * alloc_chrdev_region() - register a range of char device numbers
     * @dev: output parameter for first assigned number
     * @baseminor: first of the requested range of minor numbers
     * @count: the number of minor numbers required
     * @name: the name of the associated device or driver
     *
     * Allocates a range of char device numbers.  The major number will be
     * chosen dynamically, and returned (along with the first minor number)
     * in @dev.  Returns zero or a negative error code.
     */
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
    			const char *name)
    

    参数说明:
    dev,分配后的设备号
    baseminor,次设备号的开始值
    count,次设备的数量
    name,设备名称,但是该名称不是/dev目录下设备文件的名字,而是在/proc/devices文件当中可以查阅

    返回值:
    成功,返回0;
    失败,返回负数的错误码。

    1. 设备号的注销
    #include <linux/fs.h>
    
    /**
     * unregister_chrdev_region() - return a range of device numbers
     * @from: the first in the range of numbers to unregister
     * @count: the number of device numbers to unregister
     *
     * This function will unregister a range of @count device numbers,
     * starting with @from.  The caller should normally be the one who
     * allocated those numbers in the first place...
     */
    void unregister_chrdev_region(dev_t from, unsigned count)
    

    参数说明:
    from,注销的设备号,如果一次要注销多个设备,from就是注销设备号的开始值。
    count,次设备数量

    返回值:
    无。

    6.初始化设备

    (一)字符设备初始化

    #include <linux/fs.h>
    
    /**
     * cdev_init() - initialize a cdev structure
     * @cdev: the structure to initialize
     * @fops: the file_operations for this device
     *
     * Initializes @cdev, remembering @fops, making it ready to add to the
     * system with cdev_add().
     */
    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    

    例子:

    cdev_init(&imx6ull_led_cdev,&imx6ull_led_fops);
    

    参数说明:
    cdev,要初始化的字符设备结构体
    fops,为该字符设备添加文件操作集

    返回值:
    无。

    (二)字符设备的加入与删除

    1.字符设备加载到内核

    #include <linux/fs.h>
    /**
     * cdev_add() - add a char device to the system
     * @p: the cdev structure for the device
     * @dev: the first device number for which this device is responsible
     * @count: the number of consecutive minor numbers corresponding to this
     *         device
     *
     * cdev_add() adds the device represented by @p to the system, making it
     * live immediately.  A negative error code is returned on failure.
     */
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    

    参数说明:
    p,初始化好的字符设备
    dev,设备号
    count,次设备的数量

    返回值:
    成功,返回0;
    失败,返回负数的错误码。

    2.将字符设备从内核删除

    #include <linux/fs.h>
    
    /**
     * cdev_del() - remove a cdev from the system
     * @p: the cdev structure to be removed
     *
     * cdev_del() removes @p from the system, possibly freeing the structure
     * itself.
     */
    void cdev_del(struct cdev *p)
    

    参数说明:
    p,初始化好的字符设备

    返回值:

    #demo1操作演示:
    1.将led_drv.ko加载到内核

    [root@GEC6818 /]#insmod led_drv.ko
    [ 0110.216000] gec6818 led init
    

    2.使用lsmod命令查看模块加载状况,该命令等同于cat proc/modules操作。

    [root@GEC6818 /]#lsmod
    led_drv 2244 0 - Live 0xbf004000 
    

    第一列:模块的名称,led_drv
    第二列:模块的大小,2244
    第三列:表示依赖该模块的个数,当前为0;若有一个程序调用会自加1;若有一个模块依赖该模块,也会自加1。
    第四列:模块的运行状态,live。
    第五列:模块的加载地址,0xbf004000。

    3.使用cat命令查看/proc/devices,能够找到注册成功的设备名字。

    
    [root@GEC6818 /]#cat /proc/devices 
    Character devices:
    ......
    239 gec6818_leds
    ......
    

    4.为该设备在/dev目录下创建设备文件

    [root@GEC6818 /]#mknod /dev/gec6818_leds c 239 0
    

    说明:
    mknod 设备文件 设备类型 主设备号 次设备号

    补充说明:
    1.手动创建设备文件的时候,主设备号和次设备号不能写错,否则应用程序打开设备文件的时候,会出现以下错误信息:

    [root@GEC6818 /]#./led_test
    open /dev/gec6818_leds:: No such device or address
    

    2.若此前的设备文件已经存在,再次创建相同名字的设备文件(若该设备文件是设备号正确的),会出现以下错误:

    [root@GEC6818 /]#mknod /dev/gec6818_leds c 244 0
    mknod: /dev/gec6818_leds: File exists
    

    解决方法:先使用rm命令删除原来的设备文件,再使用mknod命令重新创建。

    [root@GEC6818 /]#rm /dev/gec6818_leds 
    [root@GEC6818 /]#mknod /dev/gec6818_leds c 244 0
    

    3检查是否创建设备文件成功

    [root@GEC6818 /]#ls -l /dev/gec6818_leds
    crw-r--r--    1 root     root      239,   0 Jan  1 05:39 gec6818_leds
    
    6.  若没有创建设备文件,会提示以下错误信息:
    
    [root@GEC6818 /]#./led_test
    open: No such file or directory
    

    7.编写应用程序

    8.使用动态分配设备号

    使用静态注册设备号,有可能导致注册失败,因为linux内核不允许两个设备使用相同的设备号,为了杜绝该问题发生,可以由内核分配空闲的设备号给我们使用。

    关键代码:

    static int __init gec6818_led_init(void)
    {
    	int rt=0;
    
    	//动态申请设备号
    	rt=alloc_chrdev_region(&led_num,0,1,"gec6818_leds");
    	
    	if(rt < 0)
    	{
    		printk("alloc_chrdev_region fail\n");
    		
    		return rt;
    	}
    	
    	printk("led_major = %d\n",MAJOR(led_num));
    	printk("led_minor = %d\n",MINOR(led_num));	
    
    ......
    }
    
    

    2.操作演示

    [root@GEC6818 /]#insmod led_drv.ko
    [10329.957000] led_major = 244
    [10329.957000] led_minor = 0
    [10329.957000] gec6818 led init
    

    9.驱动程序与应用程序进行数据交换

    在用户空间和内核空间,它们数据交换是不能直接访问的,必须通过内核提供的函数实现数据的交换。

    #include <linux/uaccess.h>
    

    1.copy_to_user
    将内核空间的数据拷贝到用户空间

    static inline long copy_to_user(void __user *to,
    		const void *from, unsigned long n)
    {
    	might_sleep();
    	if (access_ok(VERIFY_WRITE, to, n))
    		return __copy_to_user(to, from, n);
    	else
    		return n;
    }
    

    参数说明:
    to:用户空间数据的地址
    from:内核空间数据的地址
    n:要拷贝的字节数

    返回值:
    0:拷贝成功
    非0值:不能被复制的字节数

    2.copy_from_user
    将用户空间的数据拷贝到内核空间

    static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
    {
    	if (access_ok(VERIFY_READ, from, n))
    		n = __copy_from_user(to, from, n);
    	else /* security hole - plug it */
    		memset(to, 0, n);
    	return n;
    	}
    

    参数说明:
    to:内核空间数据的地址
    from:用户空间数据的地址
    n:要拷贝的字节数

    返回值:
    0:拷贝成功
    非0值:不能被复制的字节数,也就是有多少个字节没有被成功拷贝

    附:常见的错误码

    #include <linux/errno.h>
    
    #define	EPERM		 1	/* Operation not permitted */
    #define	ENOENT		 2	/* No such file or directory */
    #define	ESRCH		 3	/* No such process */
    #define	EINTR		 4	/* Interrupted system call */
    #define	EIO		 5	/* I/O error */
    #define	ENXIO		 6	/* No such device or address */
    #define	E2BIG		 7	/* Argument list too long */
    #define	ENOEXEC		 8	/* Exec format error */
    #define	EBADF		 9	/* Bad file number */
    #define	ECHILD		10	/* No child processes */
    #define	EAGAIN		11	/* Try again */
    #define	ENOMEM		12	/* Out of memory */
    #define	EACCES		13	/* Permission denied */
    #define	EFAULT		14	/* Bad address */
    #define	ENOTBLK		15	/* Block device required */
    #define	EBUSY		16	/* Device or resource busy */
    #define	EEXIST		17	/* File exists */
    #define	EXDEV		18	/* Cross-device link */
    #define	ENODEV		19	/* No such device */
    #define	ENOTDIR		20	/* Not a directory */
    #define	EISDIR		21	/* Is a directory */
    #define	EINVAL		22	/* Invalid argument */
    #define	ENFILE		23	/* File table overflow */
    #define	EMFILE		24	/* Too many open files */
    #define	ENOTTY		25	/* Not a typewriter */
    #define	ETXTBSY		26	/* Text file busy */
    #define	EFBIG		27	/* File too large */
    #define	ENOSPC		28	/* No space left on device */
    #define	ESPIPE		29	/* Illegal seek */
    #define	EROFS		30	/* Read-only file system */
    #define	EMLINK		31	/* Too many links */
    #define	EPIPE		32	/* Broken pipe */
    #define	EDOM		33	/* Math argument out of domain of func */
    #define	ERANGE		34	/* Math result not representable */
    

    注:
    若驱动层返回错误码,应用层则需要调用perror函数就能够识别到该错误码。

    #demo3关键代码及操作演示

    10.自动创建设备节点

    (一)背景
    Linux 2.6 引入了动态设备管理, 用 udev 作为设备管理器(应用在x86), 相比之前的静态设备管理,在使用上更加方便灵活。
    udev 根据 sysfs 系统提供的设备信息实现对/dev 目录下设备节点的动态管理,包括设备节点的创建、删除等。
    在当前开发板是使用udev的简化版——mdev,常用在嵌入式系统中,作用是在系统启动和热插拔或动态加载驱动程序时,自动创建设备节点。文件系统的/dev目录下的设备节点都是由mdev创建的。

    举个例子:
    insmod led_drv.ko时候,mdev自动帮我们在/dev目录下创建设备节点
    rmmod led_drv时候,mdev自动帮我们在/dev目录下删除设备节点

    若要编写一个能用 mdev 管理的设备驱动,需要在驱动代码中调用 class_create()为设备创建一个 class 类,再调用 device_create()为每个设备创建对应的设备。

    (二)参考三星公司创建类与设备的方法

    #include <linux/device.h>
    
    static struct class  *adb_dev_class;
    static struct device *adb_dev_device;
    
    static int __init adbdev_init(void)
    {
    ...............
    	if (register_chrdev(ADB_MAJOR, "adb", &adb_fops)) {
    		printk(KERN_ERR "adb: unable to get major %d\n", ADB_MAJOR);
    		return;
    	}
    
    	adb_dev_class = class_create(THIS_MODULE, "adb");
    
    	if (IS_ERR(adb_dev_class))
    		return PTR_ERR(adb_dev_class);
    
    	adb_dev_device=device_create(adb_dev_class, NULL, MKDEV(ADB_MAJOR, 0), NULL, "adb");
    
    	if (IS_ERR(adb_dev_device))
        {
            class_destroy(adb_dev_class);
            return PTR_ERR(adb_dev_device);
        }
    
        return 0;	
    }
    

    (三)创建一个类

    #include <linux/device.h>
    
    ..............................
    /* This is a #define to keep the compiler from merging different
     * instances of the __key variable */
    #define class_create(owner, name)		\
    ({						\
    	static struct lock_class_key __key;	\
    	__class_create(owner, name, &__key);	\
    })
    ..............................
    
    /**
     * class_create - create a struct class structure
     * @owner: pointer to the module that is to "own" this struct class
     * @name: pointer to a string for the name of this class.
     * @key: the lock_class_key for this class; used by mutex lock debugging
     *
     * This is used to create a struct class pointer that can then be used
     * in calls to device_create().
     *
     * Returns &struct class pointer on success, or ERR_PTR() on error.
     *
     * Note, the pointer created here is to be destroyed when finished by
     * making a call to class_destroy().
     */
    struct class *__class_create(struct module *owner, const char *name,
    			     struct lock_class_key *key)
    

    参数:
    owner:class的所有者,默认写THIS_MODULE
    name:自定义class的名字,会显示在/sys/class目录当中

    返回值:
    成功:就返回创建好的class指针
    失败:就返回错误码指针

    如何判断当前的指针为错误码,使用IS_ERR函数进行判断。

    static inline long __must_check IS_ERR(const void *ptr)
    {
    	return IS_ERR_VALUE((unsigned long)ptr);
    }
    

    如果IS_ERR返回值大于0该指针就为错误码指针。如果返回0值,就是正确的指针。

    2.创建属于class的设备

    /**
     * device_create - creates a device and registers it with sysfs
     * @class: pointer to the struct class that this device should be registered to
     * @parent: pointer to the parent struct device of this new device, if any
     * @devt: the dev_t for the char device to be added
     * @drvdata: the data to be added to the device for callbacks
     * @fmt: string for the device's name
     *
     * This function can be used by char device classes.  A struct device
     * will be created in sysfs, registered to the specified class.
     *
     * A "dev" file will be created, showing the dev_t for the device, if
     * the dev_t is not 0,0.
     * If a pointer to a parent struct device is passed in, the newly created
     * struct device will be a child of that device in sysfs.
     * The pointer to the struct device will be returned from the call.
     * Any further sysfs files that might be required can be created using this
     * pointer.
     *
     * Returns &struct device pointer on success, or ERR_PTR() on error.
     *
     * Note: the struct class passed to this function must have previously
     * been created with a call to class_create().
     */
    struct device *device_create(struct class *class, struct device *parent,
    			     dev_t devt, void *drvdata, const char *fmt, ...)
    

    参数:
    class:创建device是属于哪个类
    parent:默认为NULL
    devt:设备号,设备号必须正确,因为这个函数会在/dev目录下帮我们自动创建设备文件
    drvdata:默认为NULL
    fmt:设备的名字,如果创建成功,就可以在/dev目录看到该设备的名字

    返回值:
    成功:就返回创建好的设备指针
    失败:就返回错误码指针

    (五)类的销毁

    /**
     * class_destroy - destroys a struct class structure
     * @cls: pointer to the struct class that is to be destroyed
     *
     * Note, the pointer to be destroyed must have been created with a call
     * to class_create().
     */
    void class_destroy(struct class *cls)
    

    参数:

    class:创建device是属于哪个类

    (六)设备的销毁

    /**
     * device_destroy - removes a device that was created with device_create()
     * @class: pointer to the struct class that this device was registered with
     * @devt: the dev_t of the device that was previously registered
     *
     * This call unregisters and cleans up a device that was created with a
     * call to device_create().
     */
    void device_destroy(struct class *class, dev_t devt)
    

    参数:
    class:创建device是属于哪个类
    devt:设备号

    (七)错误码指针的判断

    static inline long __must_check IS_ERR(const void *ptr)
    {
    	return IS_ERR_VALUE((unsigned long)ptr);
    }
    

    (八)将错误码指针转换为数值(即错误码)

    static inline long __must_check PTR_ERR(const void *ptr)
    {
    	return (long) ptr; 
    }
    

    附:关键代码

    static struct class 	*leds_class;
    static struct device 	*leds_device;
    
    //入口函数
    static int __init gec6818_led_init(void)
    {
    	..................
    	//创建类
    	leds_class=class_create(THIS_MODULE, "myled");
    	
    	if (IS_ERR(leds_class))
    	{
    		rt = PTR_ERR(leds_class);
    		
    		printk("class_create gec6818_leds fail\n");
    		
    		goto fail_class_create;
    		
    	}
    	
    	//创建设备
    	leds_device=device_create(leds_class, NULL, led_num, NULL, "gec6818_leds");
    	
    	if (IS_ERR(leds_device))
    	{
    		rt = PTR_ERR(leds_device);
    		
    		printk("device_create gec6818_leds fail\n");
    		
    		goto fail_device_create;
    		
    	}	
    ..................
    }
    
    //出口函数
    static void __exit gec6818_led_exit(void)
    {
    ..................
    	device_destroy(leds_class,led_num);
    	class_destroy(leds_class);
    ..................
    }
    

    检查是否创建myled设备类型成功。

    [root@GEC6818 /sys/class]#ls
    myled
    

    检查是否在gec6818_leds类,创建gec6818_leds设备成功

    [root@GEC6818 /sys/devices/virtual/myled/gec6818_leds]#cat uevent 
    MAJOR=244
    MINOR=0
    DEVNAME=gec6818_leds
    

    检查是否创建gec6818_leds设备文件成功。

    [root@GEC6818 /]#ls /dev
    gec6818_leds
    
    展开全文
  • 字符设备、块设备、网络设备详解

    千次阅读 2019-10-09 19:23:23
    字符设备、块设备、网络设备 设备模型 设备驱动的代码量占内核程序的50% 设备模型的意义: 为了降低设备多样性带来的Linux驱动开发的复杂度,以及设备热拔插处理、电源管理等,Linux内核提出了设备模型(也称作...

    字符设备、块设备、网络设备

    在这里插入图片描述
    设备模型
    设备驱动的代码量占内核程序的50%

    设备模型的意义:
    为了降低设备多样性带来的Linux驱动开发的复杂度,以及设备热拔插处理、电源管理等,Linux内核提出了设备模型(也称作Driver Model)的概念。设备模型将硬件设备归纳、分类,然后抽象出一套标准的数据结构和接口。驱动的开发,就简化为对内核所规定的数据结构的填充和实现。

    因为硬件设备多种多样,使得设备驱动程序繁多,设备模型将硬件设备分类,抽象出一套标准的数据结构和接口。

    一、字符设备
    1.特点
    一个字节一个字节读写的设备,
    读取数据需要按照先后数据(顺序读取)
    常见的字符设备有鼠标、键盘、串口、控制台和LED设备
    每个字符设备在/dev目录下对应一个设备文件,linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备。

    2.上层应用如何调用底层驱动?
    1.应用层的程序open(“/dev/xxx”,mode,flags)打开设备文件,进入内核中,即虚拟文件系统中。
    2.VFS层的设备文件有对应的struct inode,其中包含该设备对应的设备号,设备类型,返回的设备的结构体。
    3.在驱动层中,根据设备类型和设备号就可以找到对应的设备驱动的结构体,用i_cdev保存。该结构体中有很重要的一个操作函数接口file_operations。
    4.在打开设备文件时,会分配一个struct file,将操作函数接口的地址保存在该结构体中。
    5.VFS层 向应用层返回一个fd,fd是和struct file相对应,这样,应用层可以通过fd调用操作函数,即通过驱动层调用硬件设备了。

    在这里插入图片描述
    在这里插入图片描述
    二、块设备
    1.特点
    数据以固定长度进行传输,比如512K
    从设备的任意位置(可跳)读取,但实际上,块设备会读一定长度的内容,而只返回用户要求访问的内容,所以随机访问实际上还是读了全部内容。
    块设备包括硬盘、磁盘、U盘和SD卡等
    每个块设备在/dev目录下对应一个设备文件,linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作块设备。
    块设备可以容纳文件系统,比如磁盘

    三、网络设备
    1.特点
    面向报文而不是面向流的,因此将网络接口映射到文件系统的节点比较困难
    内核调用一套和数据包相关的函数,而不是read,write。
    网络接口没有像字符设备和块设备一样的设备号,只有唯一的名字,如eth0,eth1
    主要通过socket操作,打开通常用命令行,

    2.关系
    网络协议接口层:网络层,IP
    网络设备接口层:将协议和各种网络驱动连接在一起,这一层提供一组通用函数供底层网络设备驱动使用。
    网络驱动接口层:数据链路层,提供对物理层访问的设备驱动程序,这可以是各种介质,例如串口链路或以太网设备。包括LLC和MAC层
    物理层:PHY层
    在这里插入图片描述
    ————————————————
    版权声明:本文为CSDN博主「qq_27840681」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_27840681/article/details/77567094

    展开全文
  • 1, 下列设备中属于输出设备的是 A.键盘 B.鼠标 C.显示器 D.扫描仪应该选择C:显示器。显示器(Display)又称监视器,是实现人机对话的主要工具,它既可以显示键盘输入的命令或数据,也可以显示计算机数据处理的结果。...

    1, 下列设备中属于输出设备的是 A.键盘 B.鼠标 C.显示器 D.扫描仪

    t0158b4a6abe74a340e.png.jpg

    应该选择C:显示器。显示器(Display)又称监视器,是实现人机对话的主要工具,它既可以显示键盘输入的命令或数据,也可以显示计算机数据处理的结果。常用的显示器主要有两种类型。一种是CRT(Cath-odeRayTube,阴极射线管)显示器,用於一般的台式微机;另一种是液晶(LiquidCrystalDisplay,简称LCD)显示器,用於便携式微机,下面主要介绍CRT显示器,按颜色区分,可以分为单色(黑白)显示器和彩色显示器。彩色显示器又称图形显示器,它有两种基本工作方式:字符方式和图形方式,在字符方式下,显示内容以标准字符为单位,字符的字形由点阵构成,字符点阵存放在字形发生器中。在图形方式下,显示内容以像素为单位,屏幕上的每个点(像素)均可由程序控制其亮度和颜色,因此能显示出较高质量的图形或图像。显示器的分辨率分为高中低三种,分辨率的指标是用屏幕上每行的像素数与每帧(每个屏幕画面)行数的乘积表示的。乘积越大,也就是像素点越小,数量越多,分辨率就越高,图形就越清晰美观。输出设备(Output Device)是计算机硬件系统的终端设备,用于接收计算机数据的输出显示、打印、声音、控制外围设备操作等。也是把各种计算结果数据或信息以数字、字符、图像、声音等形式表现出来。常见的输出设备有显示器、打印机、绘图仪、影像输出系统、语音输出系统、磁记录设备等。输出设备由显示器、打印机和绘图机三种组成。输出设备种类也很多.计算机常用的输出设备有各种打印机、凿孔输出设备、显示设备和绘图机等。打印机和显示设备已成为每台计算机和大多数终端所必需的设备。纸带凿孔输出机 计算机用纸带凿孔输出设备。计算机输出信息用凿孔纸带上的小孔表示。这既可将信息长期保存于纸带上,又可利用凿孔纸带再输入计算机。卡片凿孔输出机 计算机用卡片凿孔输出设备。凿孔卡片阅读方便,可长期保存,也可作为计算机的输入。参考资料:百度百科-输出设备

    2, 下列设备不是输入设备的是: A扫描仪 B数码相机 C显示器 D光电阅读...

    t017419225e929599da.png.jpg

    答案是C。A扫描仪:它可以将纸质的或者其它材质的转换为电子文档,信息是从外部流入计算机,因此是输入设备。B数码相机:它也可以将照片导入到计算机,因此也是输入型的设备。C显示器:它是用来将数据显示出来的,信息是从计算机输送到人,因此,属于输出设备。D光电阅读器:常见的答题卡就是需要光电阅读器读取的,将答题卡上的信息转换为电子信息后再输入到计算机,因此是输入设备。

    相关概念

    计算机

    电子计算机(electronic computer),通称电脑,简称计算机(computer),是现代的一种利用电子技术和相关原理根据一系列指令来对数据进行处理的机器。电脑可以分为两部分:软件系统和硬件系统。第一台电脑是1946年2月15日在美国宾夕法尼亚大学诞生的ENIAC通用电子计算机。计算机所相关的技术研究叫计算机科学,以数据为核心的研究称为信息技术。人们把没有安装任何软件的计算机称为裸机。随着科技的发展,现在新出现一些新型计算机有:生物计算机、光子计算机、量子计算机等。

    答题卡

    答题卡(又称信息卡),是光标阅读机输入信息的载体,是配套光标阅读机的各种信息录入表格的总称。信息卡将用户需要的信息转化为可选择的选项,供用户涂写。OMR设备根据信息点的涂与未涂和格式文件设置将信息还原。 信息卡的式样,是根据输入计算机的信息并按照光标阅读机的设计规范所要求的格式而设计的。

    展开全文
  • linux字符设备

    2020-02-19 14:01:33
    字符设备的定义 linux下有三种设备:字符设备、块设备、网络设备等等。它们均以一个文件节点形式显示在文件系统的/dev目录下(crw--w---- 1 root tty 4, 0 7月 11 09:11 tty0 其中c代表字符设备类型)。 字符设备是指...
  • 因此字符设备以下特定: 1、以字节流的方式进行读写,一个字节一个字节读写的设备(有先后顺序)。 2、未经过文件系统,读取速度快。 3、每个字符设备都在 “/dev/” 文件夹下有一个对应的设备文件。 二、块...
  • 设备 设备 基本上,可以自行使用,我不得不重构基本...它获取已解析的用户代理字符串的设备类型 台式机,电视,平板电脑,手机,机器人或汽车 模型 细绳 它获取已解析的用户代理字符串的设备型号名称 例如:iPhone。
  • 字符设备的上层没有磁盘文件系统,所以字符设备的file_operations成员函数就直接由字符设备驱动提供(一般字符设备都会实现相应的fops集),因此file_operations 也就成为了字符设备驱动的核心。 特点: 一个...
  • linux 设备驱动一、概述1.1 设备驱动介绍二、字符设备驱动2.1. 字符设备驱动简介2.2. LCD驱动 一、概述 1.1 设备驱动介绍 二、字符设备驱动 2.1. 字符设备驱动简介 2.2. LCD驱动 感谢阅读,祝君成功! -by aiziyou ...
  • 2、字符设备字符设备是按照字节流的方式被有序访问的,像串口和键盘就属于字符设备。 说明:块设备通过系统缓存进行读取,不是直接和物理磁盘读取。字符设备可以直接物理磁盘读取,不经过系统缓存。 3、裸设备:...
  • LinuxI/O设备分为两类:字符设备和块设备。两种设备本身没有严格限制,但是,基于不同的功能进行了分类。 (1)字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/...
  • linux新字符设备驱动

    2022-01-18 09:51:00
    linux 新字符设备驱动1 linux系统设备分类1.1 linux设备驱动之字符设备驱动linux设备驱动之字符设备驱动1.2 字符设备字符设备驱动与用户空间访问该设备的程序三者之间的关系1.3 用户空间访问该设备的程序2 字符...
  • 西电软工操作系统实验:字符设备

    千次阅读 2021-05-21 16:13:25
    编写字符设备程序,使之满足以下功能: 1、安装设备后从设备中读出字符串为你的学号; 2、设备支持每次写入字符不超过1024个,超过部分被丢弃; 3、用户可以读出最近写入到设备中的字符; 4、设备关闭前不能被多次...
  • 操作系统实验·字符设备驱动程序

    千次阅读 2021-12-10 23:08:56
    编写一个简单的字符设备驱动程序,该字符设备并不驱动特定的硬件, 而是用内核空间模拟字符设备,要求该字符设备包括以下几个基本操作,打开、读、写和释放,并编写测试程序用于测试所编写的字符设备驱动程序。...
  •  学习Linux设备驱动开发的过程自然会遇到字符设备驱动、平台设备驱动、设备驱动模型和sysfs等相关概念和技术。  对于初学者来说会非常困惑,甚至对Linux有一定基础的工程师而言,能够较好理解这些相关技术也相对...
  • GPIO驱动可以归类为Linux设备驱动的字符设备驱动,以下是开发它的一些具体步骤。  (1)模块化驱动程序  不失Linux驱动开发的一般性,在写字符设备的驱动程序时,也要遵守模块化编程的一般规范。设备模块在用户...
  • 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。块设备:是指可以...
  • 3种字符设备驱动编程

    千次阅读 2022-01-29 17:16:08
    1 杂项字符设备 核心结构体 操作函数 杂项字符设备注册应用 2 早期经典方式注册 操作函数 经典设备注册应用 3 Linux2.6字符设备 核心结构体 操作函数 Linux2.6设备注册应用 Linux系统借鉴了面向对象的...
  • 如何编写字符设备驱动

    千次阅读 2018-11-15 17:43:43
    0.包含必须的头文件 #include &lt;linux/fs.h&gt; #include &lt;linux/cdev.h&gt; #include &lt;linux/slab.h&...1.编写字符设备函数 1.1、分配核心结构 struct cdev *cd...
  • 字符设备是指以字符为单位进行I/O传输的设备,例如键盘,打印机等,字符设备也拥有相应的结点。字符设备可以使用与普通文件相同的文件操作命令对字符设备文件进行打开、关闭、读写等操作。字符设备和杂项设备的区别...
  • Linux字符设备驱动剖析

    千次阅读 2015-05-23 23:09:13
    忠于源码,讲述linux字符设备驱动的那些事儿,重点讲述字符设备的创建和访问过程。
  • 02、字符设备驱动

    千次阅读 2021-09-26 14:09:08
      在Linux内核,使用cdev结构体描述一个字符设备,cdev结构体的定义在<linux/cdev.h>头文件声明如下: struct cdev { struct kobject kobj; struct module *owner; /* 所属模块 */ const struct ...
  • linux字符设备驱动学习笔记借鉴.pdf
  • linux字符设备驱动学习笔记文.pdf
  • 在Linux内核驱动字符设备是最基本的设备驱动。字符设备包括了设备最基本的操作,如打开设备、关闭设备、I/O控制等。学习其他设备驱动最好从字符设备开始。字符设备是能够像字节流(比如文件)一样被访问的设备,...
  • Linux字符设备驱动程序编写基本流程Linux字符设备驱动程序编写基本流程
  • Linux设备驱动之字符设备驱动

    万次阅读 多人点赞 2016-07-01 19:36:15
    字符设备:只能一个字节一个字节的读写的设备,不能随机读取设备内存的某一数据,读取数据需要按照先后顺序进行。字符设备是面向流的设备,常见的字符设备如鼠标、键盘、串口、控制台、LED等。 块设备:是指可以...
  • linux字符设备驱动框架(三)

    万次阅读 2018-11-21 10:56:53
    版权声明:本文为博主原创文章,未经博主允许不得转载。 ... 一、字符设备基础知识 1、设备驱动分类 ... linux系统将设备分为3类:字符设备、块设备...
  • 横向比较关联各个驱动相关的知识点(字符设备驱动、平台设备驱动、设备驱动模型、sysfs)和纵向剖析Linux整个驱动软件层次,对于Linux驱动的理解和开发很有帮助,绝对干货!

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 289,323
精华内容 115,729
关键字:

以下设备中属于字符设备的是