精华内容
下载资源
问答
  •  linux系统将设备分为3类:字符设备、块设备、网络设备。使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的...

    一、字符设备基础知识

    1、设备驱动分类

          linux系统将设备分为3类:字符设备、块设备、网络设备。使用驱动程序:



    字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。

    块设备:是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。

    每一个字符设备或块设备都在/dev目录下对应一个设备文件linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备


    2、字符设备、字符设备驱动与用户空间访问该设备的程序三者之间的关系


         如图,在Linux内核中:

    a -- 使用cdev结构体来描述字符设备;

    b -- 通过其成员dev_t来定义设备号(分为主、次设备号)以确定字符设备的唯一性;

    c -- 通过其成员file_operations来定义字符设备驱动提供给VFS的接口函数,如常见的open()、read()、write()等;


         在Linux字符设备驱动中:

    a -- 模块加载函数通过 register_chrdev_region( ) 或 alloc_chrdev_region( )来静态或者动态获取设备号;

    b -- 通过 cdev_init( ) 建立cdev与 file_operations之间的连接,通过 cdev_add( ) 向系统添加一个cdev以完成注册;

    c -- 模块卸载函数通过cdev_del( )来注销cdev,通过 unregister_chrdev_region( )来释放设备号;


         用户空间访问该设备的程序:

    a -- 通过Linux系统调用,如open( )、read( )、write( ),来“调用”file_operations来定义字符设备驱动提供给VFS的接口函数;


    3、字符设备驱动模型



    二、cdev 结构体解析

          在Linux内核中,使用cdev结构体来描述一个字符设备,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;                   //隶属于同一主设备号的次设备号的个数.
    };

    内核给出的操作struct cdev结构的接口主要有以下几个:

    a -- void cdev_init(struct cdev *, const struct file_operations *);

    其源代码如代码清单如下:

    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    {
    	memset(cdev, 0, sizeof *cdev);
    	INIT_LIST_HEAD(&cdev->list);
    	kobject_init(&cdev->kobj, &ktype_cdev_default);
    	cdev->ops = fops;
    }
    
          该函数主要对struct cdev结构体做初始化最重要的就是建立cdev 和 file_operations之间的连接:

    (1) 将整个结构体清零;

    (2) 初始化list成员使其指向自身;

    (3) 初始化kobj成员;

    (4) 初始化ops成员;


     b --struct cdev *cdev_alloc(void);

         该函数主要分配一个struct cdev结构动态申请一个cdev内存,并做了cdev_init中所做的前面3步初始化工作(第四步初始化工作需要在调用cdev_alloc后,显式的做初始化即: .ops=xxx_ops).

    其源代码清单如下:

    struct cdev *cdev_alloc(void)
    {
    	struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
    	if (p) {
    		INIT_LIST_HEAD(&p->list);
    		kobject_init(&p->kobj, &ktype_cdev_dynamic);
    	}
    	return p;
    }

         在上面的两个初始化的函数中,我们没有看到关于owner成员、dev成员、count成员的初始化;其实,owner成员的存在体现了驱动程序与内核模块间的亲密关系,struct module是内核对于一个模块的抽象,该成员在字符设备中可以体现该设备隶属于哪个模块,在驱动程序的编写中一般由用户显式的初始化 .owner = THIS_MODULE, 该成员可以防止设备的方法正在被使用时,设备所在模块被卸载。而dev成员和count成员则在cdev_add中才会赋上有效的值。

     
    c -- int cdev_add(struct cdev *p, dev_t dev, unsigned count);

           该函数向内核注册一个struct cdev结构,即正式通知内核由struct cdev *p代表的字符设备已经可以使用了。

    当然这里还需提供两个参数:

    (1)第一个设备号 dev,

    (2)和该设备关联的设备编号的数量。

    这两个参数直接赋值给struct cdev 的dev成员和count成员。


    d -- void cdev_del(struct cdev *p);

         该函数向内核注销一个struct cdev结构,即正式通知内核由struct cdev *p代表的字符设备已经不可以使用了。

         从上述的接口讨论中,我们发现对于struct cdev的初始化和注册的过程中,我们需要提供几个东西

    (1) struct file_operations结构指针;

    (2) dev设备号;

    (3) count次设备号个数。

    但是我们依旧不明白这几个值到底代表着什么,而我们又该如何去构造这些值!



    三、设备号相应操作

    1 -- 主设备号和次设备号(二者一起为设备号):

          一个字符设备或块设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。

      linux内核中,设备号用dev_t来描述,2.6.28中定义如下:

      typedef u_long dev_t;

      在32位机中是4个字节,高12位表示主设备号,低20位表示次设备号。

    内核也为我们提供了几个方便操作的宏实现dev_t:

    1) -- 从设备号中提取major和minor

    MAJOR(dev_t dev);                              

    MINOR(dev_t dev);

    2) -- 通过major和minor构建设备号

    MKDEV(int major,int minor);

    注:这只是构建设备号。并未注册,需要调用 register_chrdev_region 静态申请;

    //宏定义:
    #define MINORBITS    20
    #define MINORMASK    ((1U << MINORBITS) - 1)
    #define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))
    #define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))
    #define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))</span>


    2、分配设备号(两种方法):

    a -- 静态申请

    int register_chrdev_region(dev_t from, unsigned count, const char *name);

    其源代码清单如下:

    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    {
    	struct char_device_struct *cd;
    	dev_t to = from + count;
    	dev_t n, next;
    
    	for (n = from; n < to; n = next) {
    		next = MKDEV(MAJOR(n)+1, 0);
    		if (next > to)
    			next = to;
    		cd = __register_chrdev_region(MAJOR(n), MINOR(n),
    			       next - n, name);
    		if (IS_ERR(cd))
    			goto fail;
    	}
    	return 0;
    fail:
    	to = n;
    	for (n = from; n < to; n = next) {
    		next = MKDEV(MAJOR(n)+1, 0);
    		kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
    	}
    	return PTR_ERR(cd);
    }

    b -- 动态分配:

    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

    其源代码清单如下:

    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
    			const char *name)
    {
    	struct char_device_struct *cd;
    	cd = __register_chrdev_region(0, baseminor, count, name);
    	if (IS_ERR(cd))
    		return PTR_ERR(cd);
    	*dev = MKDEV(cd->major, cd->baseminor);
    	return 0;
    }

    可以看到二者都是调用了__register_chrdev_region 函数,其源代码如下:

    static struct char_device_struct *
    __register_chrdev_region(unsigned int major, unsigned int baseminor,
    			   int minorct, const char *name)
    {
    	struct char_device_struct *cd, **cp;
    	int ret = 0;
    	int i;
    
    	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
    	if (cd == NULL)
    		return ERR_PTR(-ENOMEM);
    
    	mutex_lock(&chrdevs_lock);
    
    	/* temporary */
    	if (major == 0) {
    		for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
    			if (chrdevs[i] == NULL)
    				break;
    		}
    
    		if (i == 0) {
    			ret = -EBUSY;
    			goto out;
    		}
    		major = i;
    		ret = major;
    	}
    
    	cd->major = major;
    	cd->baseminor = baseminor;
    	cd->minorct = minorct;
    	strlcpy(cd->name, name, sizeof(cd->name));
    
    	i = major_to_index(major);
    
    	for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
    		if ((*cp)->major > major ||
    		    ((*cp)->major == major &&
    		     (((*cp)->baseminor >= baseminor) ||
    		      ((*cp)->baseminor + (*cp)->minorct > baseminor))))
    			break;
    
    	/* Check for overlapping minor ranges.  */
    	if (*cp && (*cp)->major == major) {
    		int old_min = (*cp)->baseminor;
    		int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
    		int new_min = baseminor;
    		int new_max = baseminor + minorct - 1;
    
    		/* New driver overlaps from the left.  */
    		if (new_max >= old_min && new_max <= old_max) {
    			ret = -EBUSY;
    			goto out;
    		}
    
    		/* New driver overlaps from the right.  */
    		if (new_min <= old_max && new_min >= old_min) {
    			ret = -EBUSY;
    			goto out;
    		}
    	}
    
    	cd->next = *cp;
    	*cp = cd;
    	mutex_unlock(&chrdevs_lock);
    	return cd;
    out:
    	mutex_unlock(&chrdevs_lock);
    	kfree(cd);
    	return ERR_PTR(ret);
    }
     通过这个函数可以看出 register_chrdev_region alloc_chrdev_region 的区别,register_chrdev_region直接将Major 注册进入,而 alloc_chrdev_region从Major = 0 开始,逐个查找设备号,直到找到一个闲置的设备号,并将其注册进去;

    二者应用可以简单总结如下:

                                         register_chrdev_region                                                alloc_chrdev_region 

        devno = MKDEV(major,minor);
        ret = register_chrdev_region(devno, 1, "hello"); 
        cdev_init(&cdev,&hello_ops);
        ret = cdev_add(&cdev,devno,1);
        alloc_chrdev_region(&devno, minor, 1, "hello");
        major = MAJOR(devno);
        cdev_init(&cdev,&hello_ops);
        ret = cdev_add(&cdev,devno,1)
    register_chrdev(major,"hello",&hello

         可以看到,除了前面两个函数,还加了一个register_chrdev 函数,可以发现这个函数的应用非常简单,只要一句就可以搞定前面函数所做之事;

    下面分析一下register_chrdev 函数,其源代码定义如下:

    static inline int register_chrdev(unsigned int major, const char *name,
    				  const struct file_operations *fops)
    {
    	return __register_chrdev(major, 0, 256, name, fops);
    }
    调用了 __register_chrdev(major, 0, 256, name, fops) 函数:
    int __register_chrdev(unsigned int major, unsigned int baseminor,
    		      unsigned int count, const char *name,
    		      const struct file_operations *fops)
    {
    	struct char_device_struct *cd;
    	struct cdev *cdev;
    	int err = -ENOMEM;
    
    	cd = __register_chrdev_region(major, baseminor, count, name);
    	if (IS_ERR(cd))
    		return PTR_ERR(cd);
    
    	cdev = cdev_alloc();
    	if (!cdev)
    		goto out2;
    
    	cdev->owner = fops->owner;
    	cdev->ops = fops;
    	kobject_set_name(&cdev->kobj, "%s", name);
    
    	err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
    	if (err)
    		goto out;
    
    	cd->cdev = cdev;
    
    	return major ? 0 : cd->major;
    out:
    	kobject_put(&cdev->kobj);
    out2:
    	kfree(__unregister_chrdev_region(cd->major, baseminor, count));
    	return err;
    }
    可以看到这个函数不只帮我们注册了设备号,还帮我们做了cdev 的初始化以及cdev 的注册;

    3、注销设备号:

    void unregister_chrdev_region(dev_t from, unsigned count);


    4、创建设备文件:

         利用cat /proc/devices查看申请到的设备名,设备号。

    1)使用mknod手工创建:mknod filename type major minor

    2)自动创建设备节点:

        利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置。在驱动初始化代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。

        详细解析见:Linux 字符设备驱动开发 (二)—— 自动创建设备节点


    下面看一个实例,练习一下上面的操作:

    hello.c

    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    static int major = 250;
    static int minor = 0;
    static dev_t devno;
    static struct cdev cdev;
    static int hello_open (struct inode *inode, struct file *filep)
    {
    	printk("hello_open \n");
    	return 0;
    }
    static struct file_operations hello_ops=
    {
    	.open = hello_open,			
    };
    
    static int hello_init(void)
    {
    	int ret;	
    	printk("hello_init");
    	devno = MKDEV(major,minor);
    	ret = register_chrdev_region(devno, 1, "hello");
    	if(ret < 0)
    	{
    		printk("register_chrdev_region fail \n");
    		return ret;
    	}
    	cdev_init(&cdev,&hello_ops);
    	ret = cdev_add(&cdev,devno,1);
    	if(ret < 0)
    	{
    		printk("cdev_add fail \n");
    		return ret;
    	}	
    	return 0;
    }
    static void hello_exit(void)
    {
    	cdev_del(&cdev);
    	unregister_chrdev_region(devno,1);
    	printk("hello_exit \n");
    }
    MODULE_LICENSE("GPL");
    module_init(hello_init);
    module_exit(hello_exit);
    

    测试程序 test.c

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    
    main()
    {
    	int fd;
    
    	fd = open("/dev/hello",O_RDWR);
    	if(fd<0)
    	{
    		perror("open fail \n");
    		return ;
    	}
    
    	close(fd);
    }
    makefile:
    ifneq  ($(KERNELRELEASE),)
    obj-m:=hello.o
    $(info "2nd")
    else
    KDIR := /lib/modules/$(shell uname -r)/build
    PWD:=$(shell pwd)
    all:
    	$(info "1st")
    	make -C $(KDIR) M=$(PWD) modules
    clean:
    	rm -f *.ko *.o *.symvers *.mod.c *.mod.o *.order
    endif

    编译成功后,使用 insmod 命令加载:

    然后用cat /proc/devices 查看,会发现设备号已经申请成功;



    展开全文
  • 前面我们学习了字符设备结构体cdev Linux 字符设备驱动开发 (一)—— 字符设备驱动结构(上) 下面继续学习字符设备另外几个重要的数据结构。  先看下面这张图:

           前面我们学习了字符设备结构体cdev Linux 字符设备驱动开发 (一)—— 字符设备驱动结构(上)  下面继续学习字符设备另外几个重要的数据结构。

           先看下面这张图,这是Linux 中虚拟文件系统、一般的设备文件与设备驱动程序值间的函数调用关系;



            上面这张图展现了一个应用程序调用字符设备驱动的过程, 在设备驱动程序的设计中,一般而言,会关心 file 和 inode 这两个结构体

            用户空间使用 open() 函数打开一个字符设备 fd = open("/dev/hello",O_RDWR) , 这一函数会调用两个数据结构 struct inode{...}struct file{...} ,二者均在虚拟文件系统VFS处,下面对两个数据结构进行解析:


    一、file 文件结构体

           在设备驱动中,这也是个非常重要的数据结构,必须要注意一点,这里的file与用户空间程序中的FILE指针是不同的,用户空间FILE是定义在C库中,从来不会出现在内核中。而struct file,却是内核当中的数据结构,因此,它也不会出现在用户层程序中。

           file结构体指示一个已经打开的文件(设备对应于设备文件),其实系统中的每个打开的文件在内核空间都有一个相应的struct file结构体,它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数,直至文件被关闭。如果文件被关闭,内核就会释放相应的数据结构。

         在内核源码中,struct file要么表示为file,或者为filp(意指“file pointer”), 注意区分一点,file指的是struct file本身,而filp是指向这个结构体的指针。

    下面是几个重要成员:

    a -- fmode_t f_mode;

          此文件模式通过FMODE_READ, FMODE_WRITE识别了文件为可读的,可写的,或者是二者。在open或ioctl函数中可能需要检查此域以确认文件的读/写权限,你不必直接去检测读或写权限,因为在进行octl等操作时内核本身就需要对其权限进行检测。

     b -- loff_t f_pos;

         当前读写文件的位置。为64位。如果想知道当前文件当前位置在哪,驱动可以读取这个值而不会改变其位置。对read,write来说,当其接收到一个loff_t型指针作为其最后一个参数时,他们的读写操作便作更新文件的位置,而不需要直接执行filp ->f_pos操作。而llseek方法的目的就是用于改变文件的位置。

    c -- unsigned int f_flags;

         文件标志,如O_RDONLY, O_NONBLOCK以及O_SYNC。在驱动中还可以检查O_NONBLOCK标志查看是否有非阻塞请求。其它的标志较少使用。特别地注意的是,读写权限的检查是使用f_mode而不是f_flog。所有的标量定义在头文件中

    d -- struct file_operations *f_op;

        与文件相关的各种操作。当文件需要迅速进行各种操作时,内核分配这个指针作为它实现文件打开,读,写等功能的一部分。filp->f_op 其值从未被内核保存作为下次的引用,即你可以改变与文件相关的各种操作,这种方式效率非常高。

        file_operation 结构体解析如下:Linux 字符设备驱动结构(四)—— file_operations 结构体知识解析

    e -- void *private_data;

          在驱动调用open方法之前,open系统调用设置此指针为NULL值。你可以很自由的将其做为你自己需要的一些数据域或者不管它,如,你可以将其指向一个分配好的数据,但是你必须记得在file struct被内核销毁之前在release方法中释放这些数据的内存空间。private_data用于在系统调用期间保存各种状态信息是非常有用的。



    二、 inode结构体

             VFS inode 包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。它是Linux 管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。

            内核使用inode结构体在内核内部表示一个文件。因此,它与表示一个已经打开的文件描述符的结构体(即file 文件结构)是不同的,我们可以使用多个file 文件结构表示同一个文件的多个文件描述符,但此时,所有的这些file文件结构全部都必须只能指向一个inode结构体

          inode结构体包含了一大堆文件相关的信息,但是就针对驱动代码来说,我们只要关心其中的两个域即可:

    (1) dev_t i_rdev;

          表示设备文件的结点,这个域实际上包含了设备号

    (2) struct cdev *i_cdev;

          struct cdev是内核的一个内部结构,它是用来表示字符设备的,当inode结点指向一个字符设备文件时,此域为一个指向inode结构的指针。

    下面是源代码:

    struct inode {
     struct hlist_node i_hash;
     struct list_head i_list;
     struct list_head i_sb_list;
     struct list_head i_dentry;
     unsigned long  i_ino;
     atomic_t  i_count;
     unsigned int  i_nlink;
     uid_t   i_uid;//inode拥有者id
     gid_t   i_gid;//inode所属群组id
     dev_t   i_rdev;//若是设备文件,表示记录设备的设备号
     u64   i_version;
     loff_t   i_size;//inode所代表大少
    #ifdef __NEED_I_SIZE_ORDERED
     seqcount_t  i_size_seqcount;
    #endif
     struct timespec  i_atime;//inode最近一次的存取时间
     struct timespec  i_mtime;//inode最近一次修改时间
     struct timespec  i_ctime;//inode的生成时间
     unsigned int  i_blkbits;
     blkcnt_t  i_blocks;
     unsigned short          i_bytes;
     umode_t   i_mode;
     spinlock_t  i_lock; 
     struct mutex  i_mutex;
     struct rw_semaphore i_alloc_sem;
     const struct inode_operations *i_op;
     const struct file_operations *i_fop; 
     struct super_block *i_sb;
     struct file_lock *i_flock;
     struct address_space *i_mapping;
     struct address_space i_data;
    #ifdef CONFIG_QUOTA
     struct dquot  *i_dquot[MAXQUOTAS];
    #endif
     struct list_head i_devices;
     union {
      struct pipe_inode_info *i_pipe;
      struct block_device *i_bdev;
      struct cdev  *i_cdev;//若是字符设备,对应的为cdev结构体
     };


    三、chardevs 数组

         从图中可以看出,通过数据结构 struct inode{...} 中的 i_cdev 成员可以找到cdev,而所有的字符设备都在 chrdevs 数组中

    下面先看一下 chrdevs 的定义:

    #define CHRDEV_MAJOR_HASH_SIZE 255
    static DEFINE_MUTEX(chrdevs_lock);
    
    static struct char_device_struct {
    	struct char_device_struct *next; // 结构体指针
    	unsigned int major;              // 主设备号
    	unsigned int baseminor;          // 次设备起始号
    	int minorct;                     // 次备号个数
    	char name[64];
    	struct cdev *cdev; /* will die */
    } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];      // 只能挂255个字符主设备<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">  </span>

           可以看到全局数组 chrdevs 包含了255(CHRDEV_MAJOR_HASH_SIZE 的值)个 struct char_device_struct的元素,每一个对应一个相应的主设备号。

           如果分配了一个设备号,就会创建一个 struct char_device_struct 的对象,并将其添加到 chrdevs 中;这样,通过chrdevs数组,我们就可以知道分配了哪些设备号。


    相关函数,(这些函数在上篇已经介绍过,现在回顾一下:

      register_chrdev_region( ) 分配指定的设备号范围

      alloc_chrdev_region( ) 动态分配设备范围

    他们都主要是通过调用函数 __register_chrdev_region() 来实现的;要注意,这两个函数仅仅是注册设备号!如果要和cdev关联起来,还要调用cdev_add()。

      register_chrdev( )申请指定的设备号,并且将其注册到字符设备驱动模型中.

      它所做的事情为:

    a -- 注册设备号, 通过调用 __register_chrdev_region() 来实现

    b -- 分配一个cdev, 通过调用 cdev_alloc() 来实现

    c -- 将cdev添加到驱动模型中, 这一步将设备号和驱动关联了起来. 通过调用 cdev_add() 来实现

    d -- 将第一步中创建的 struct char_device_struct 对象的 cdev 指向第二步中分配的cdev. 由于register_chrdev()是老的接口,这一步在新的接口中并不需要。


    四、cdev 结构体

            在 Linux 字符设备驱动开发 (一)—— 字符设备驱动结构(上) 有解析。


    五、文件系统中对字符设备文件的访问

            下面看一下上层应用open() 调用系统调用函数的过程

            对于一个字符设备文件, 其inode->i_cdev 指向字符驱动对象cdev, 如果i_cdev为 NULL ,则说明该设备文件没有被打开.

      由于多个设备可以共用同一个驱动程序.所以,通过字符设备的inode 中的i_devices 和 cdev中的list组成一个链表


            首先,系统调用open打开一个字符设备的时候, 通过一系列调用,最终会执行到 chrdev_open

      (最终是通过调用到def_chr_fops中的.open, 而def_chr_fops.open = chrdev_open. 这一系列的调用过程,本文暂不讨论)

      int chrdev_open(struct inode * inode, struct file * filp)

    chrdev_open()所做的事情可以概括如下:

      1. 根据设备号(inode->i_rdev), 在字符设备驱动模型中查找对应的驱动程序, 这通过kobj_lookup() 来实现, kobj_lookup()会返回对应驱动程序cdev的kobject.

      2. 设置inode->i_cdev , 指向找到的cdev.

      3. 将inode添加到cdev->list 的链表中.

      4. 使用cdev的ops 设置file对象的f_op

      5. 如果ops中定义了open方法,则调用该open方法

      6. 返回

    执行完 chrdev_open()之后,file对象的f_op指向cdev的ops,因而之后对设备进行的read, write等操作,就会执行cdev的相应操作。



    展开全文
  • 前面在 Linux 字符设备驱动开发基础 (三)—— 字符设备驱动结构(中) ,我们已经介绍了两种重要的数据结构 struct inode{...}与 struct file{...} ,下面来介绍另一个比较重要数据结构 struct _file_...

            前面在 Linux 字符设备驱动开发基础 (三)—— 字符设备驱动结构(中) ,我们已经介绍了两种重要的数据结构 struct inode{...} struct file{...} ,下面来介绍另一个比较重要数据结构

    struct _file_operations

    struct _file_operations在Fs.h这个文件里面被定义的,如下所示:

    struct file_operations { 
    
        struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES 
    
    
        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 *);//向设备发送数据
    
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的读取操作 
    
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的写入操作 
    
        int (*readdir) (struct file *, void *, filldir_t);//仅用于读取目录,对于设备文件,该字段为NULL 
    
        unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入 
    
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令 
    
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl 
    
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替 
    
    
        int (*mmap) (struct file *, struct vm_area_struct *); //用于请求将设备内存映射到进程地址空间
     
        int (*open) (struct inode *, struct file *); //打开 
    
        int (*flush) (struct file *, fl_owner_t id); 
    
        int (*release) (struct inode *, struct file *); //关闭 
    
        int (*fsync) (struct file *, struct dentry *, int datasync); //刷新待处理的数据 
    
        int (*aio_fsync) (struct kiocb *, int datasync); //异步刷新待处理的数据 
    
        int (*fasync) (int, struct file *, int); //通知设备FASYNC标志发生变化 
    
        int (*lock) (struct file *, int, struct file_lock *); 
    
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); 
    
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); 
    
        int (*check_flags)(int); 
    
        int (*flock) (struct file *, int, struct file_lock *);
     
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
     
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); 
    
        int (*setlease)(struct file *, long, struct file_lock **); 
    
    };

          Linux使用file_operations结构访问驱动程序的函数,这个结构的每一个成员的名字都对应着一个调用。

         用户进程利用在对设备文件进行诸如read/write操作的时候,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数,这是Linux的设备驱动程序工作的基本原理。


    下面是各成员解析:

    1、struct module *owner

           第一个 file_operations 成员根本不是一个操作,它是一个指向拥有这个结构的模块的指针。

          这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.这个宏比较复杂,在进行简单学习操作的时候,一般初始化为THIS_MODULE。


    2、loff_t (*llseek) (struct file * filp , loff_t p, int orig);

          (指针参数filp为进行读取信息的目标文件结构体指针;参数 p 为文件定位的目标偏移量;参数orig为对文件定位的起始地址,这个值可以为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2))

          llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.

    loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示;如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).


    3、ssize_t (*read) (struct file * filp, char __user * buffer, size_t    size , loff_t * p);

        (指针参数 filp 为进行读取信息的目标文件,指针参数buffer 为对应放置信息的缓冲区(即用户空间内存地址),参数size为要读取的信息长度,参数 p 为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值)

         这个函数用来从设备中获取数据。在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败。一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).


    4、ssize_t (*aio_read)(struct kiocb * , char __user * buffer, size_t size , loff_t   p);

          可以看出,这个函数的第一、三个参数和本结构体中的read()函数的第一、三个参数是不同 的,异步读写的第三个参数直接传递值,而同步读写的第三个参数传递的是指针,因为AIO从来不需要改变文件的位置。异步读写的第一个参数为指向kiocb结构体的指针,而同步读写的第一参数为指向file结构体的指针,每一个I/O请求都对应一个kiocb结构体);初始化一个异步读 -- 可能在函数返回前不结束的读操作.如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).(有关linux异步I/O,可以参考有关的资料,《linux设备驱动开发详解》中给出了详细的解答)


    5、ssize_t (*write) (struct file * filp, const char __user *   buffer, size_t count, loff_t * ppos);

         (参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度,ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界)

         发送数据给设备.。如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数。

         (注:这个操作和上面的对文件进行读的操作均为阻塞操作


    6、ssize_t (*aio_write)(struct kiocb *, const char __user * buffer, size_t count, loff_t * ppos);

          初始化设备上的一个异步写.参数类型同aio_read()函数;


    7、int (*readdir) (struct file * filp, void *, filldir_t);

          对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.


    8、unsigned int (*poll) (struct file *, struct poll_table_struct *);

         (这是一个设备驱动中的轮询函数,第一个参数为file结构指针,第二个为轮询表指针)

         这个函数返回设备资源的可获取状态,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位“或”结果。每个宏都表明设备的一种状态,如:POLLIN(定义为0x0001)意味着设备可以无阻塞的读,POLLOUT(定义为0x0004)意味着设备可以无阻塞的写。

         (poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞.poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写.

        (这里通常将设备看作一个文件进行相关的操作,而轮询操作的取值直接关系到设备的响应情况,可以是阻塞操作结果,同时也可以是非阻塞操作结果)


    9、int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

           (inode 和 filp 指针是对应应用程序传递的文件描述符 fd 的值, 和传递给 open 方法的相同参数.cmd 参数从用户那里不改变地传下来, 并且可选的参数 arg 参数以一个 unsigned long 的形式传递, 不管它是否由用户给定为一个整数或一个指针.如果调用程序不传递第 3 个参数, 被驱动操作收到的 arg 值是无定义的.因为类型检查在这个额外参数上被关闭, 编译器不能警告你如果一个无效的参数被传递给 ioctl, 并且任何关联的错误将难以查找.)

          ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表.如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.


    10、int (*mmap) (struct file *, struct vm_area_struct *);

            mmap 用来请求将设备内存映射到进程的地址空间。 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.

           (如果想对这个函数有个彻底的了解,那么请看有关“进程地址空间”介绍的书籍)


    11、int (*open) (struct inode * inode , struct file * filp ) ;

            (inode 为文件节点,这个节点只有一个,无论用户打开多少个文件,都只是对应着一个inode结构;但是filp就不同,只要打开一个文件,就对应着一个file结构体,file结构体通常用来追踪文件在运行时的状态信息)

           尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.与open()函数对应的是release()函数。


    12、int (*flush) (struct file *);

            flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 

           它应当执行(并且等待)设备的任何未完成的操作.这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用;SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.


    13、int (*release) (struct inode *, struct file *);

            release ()函数当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数:

           void release(struct inode inode,struct file *file),release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.


    14、int(*synch)(struct file *,struct dentry *,int datasync);

           刷新待处理的数据,允许进程把所有的脏缓冲区刷新到磁盘。


    15、int (*aio_fsync)(struct kiocb *, int);

            这是 fsync 方法的异步版本.所谓的fsync方法是一个系统调用函数。系统调用fsync把文件所指定的文件的所有脏缓冲区写到磁盘中(如果需要,还包括存有索引节点的缓冲区)。相应的服务例程获得文件对象的地址,并随后调用fsync方法。通常这个方法以调用函数__writeback_single_inode()结束,这个函数把与被选中的索引节点相关的脏页和索引节点本身都写回磁盘


    16、int (*fasync) (int, struct file *, int);

            这个函数是系统支持异步通知的设备驱动,下面是这个函数的模板:

    static int ***_fasync(int fd,struct file *filp,int mode)
    {
        struct ***_dev * dev=filp->private_data;
        return fasync_helper(fd,filp,mode,&dev->async_queue);//第四个参数为 fasync_struct结构体指针的指针。
    //这个函数是用来处理FASYNC标志的函数。(FASYNC:表示兼容BSD的fcntl同步操作)当这个标志改变时,驱动程序中的fasync()函数将得到执行。  (注:感觉这个‘标志'词用的并不恰当)
    }
    此操作用来通知设备它的 FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述.这个成员可以是NULL 如果驱动不支持异步通知.


    17、int (*lock) (struct file *, int, struct file_lock *);

            lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.


    18、ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);

            ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);

    这些方法实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作;这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 ).


    19、ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);

           这个方法实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个.

           例如, 它被一个需要发送文件内容到一个网络连接的 web 服务器使用. 设备驱动常常使 sendfile 为 NULL.


    20、ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

            sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现 sendpage.


    21、unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

             这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中。这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.[10]


    22、int (*check_flags)(int)

           这个方法允许模块检查传递给 fnctl(F_SETFL...) 调用的标志.


    23、int (*dir_notify)(struct file *, unsigned long);

            这个方法在应用程序使用 fcntl 来请求目录改变通知时调用. 只对文件系统有用; 驱动不需要实现 dir_notify.



    展开全文
  • 上一篇我们介绍到创建设备文件的方法,利用cat /proc/devices查看申请到的设备名,设备号。 第一种是使用mknod手工创建:mknod filename type major minor ...在驱动初始化代码里调用class_create为该设备创建一个

          上一篇我们介绍到创建设备文件的方法,利用cat /proc/devices查看申请到的设备名,设备号。

    第一种是使用mknod手工创建:mknod filename type major minor

    第二种是自动创建设备节点:利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置。

          具体udev相关知识这里不详细阐述,可以移步Linux 文件系统与设备文件系统 —— udev 设备文件系统,这里主要讲使用方法。

         

        在驱动用加入对udev 的支持主要做的就是:在驱动初始化的代码里调用class_create(...)为该设备创建一个class,再为每个设备调用device_create(...)创建对应的设备

        内核中定义的struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用 device_create(…)函数来在/dev目录下创建相应的设备节点。

         这样,加载模块的时候,用户空间中的udev会自动响应 device_create()函数,去/sysfs下寻找对应的类从而创建设备节点。


    下面是两个函数的解析:

    1、class_create(...) 函数

    功能:创建一个类;

    下面是具体定义:

    #define class_create(owner, name)		\
    ({						\
    	static struct lock_class_key __key;	\
    	__class_create(owner, name, &__key);	\
    })
    

    owner:THIS_MODULE
    name  : 名字

    __class_create(owner, name, &__key)源代码如下:

    struct class *__class_create(struct module *owner, const char *name,
    			     struct lock_class_key *key)
    {
    	struct class *cls;
    	int retval;
    
    	cls = kzalloc(sizeof(*cls), GFP_KERNEL);
    	if (!cls) {
    		retval = -ENOMEM;
    		goto error;
    	}
    
    	cls->name = name;
    	cls->owner = owner;
    	cls->class_release = class_create_release;
    
    	retval = __class_register(cls, key);
    	if (retval)
    		goto error;
    
    	return cls;
    
    error:
    	kfree(cls);
    	return ERR_PTR(retval);
    }
    EXPORT_SYMBOL_GPL(__class_create);

    销毁函数:void class_destroy(struct class *cls)

    void class_destroy(struct class *cls)
    {
    	if ((cls == NULL) || (IS_ERR(cls)))
    		return;
    
    	class_unregister(cls);
    }


    2、device_create(...) 函数

    struct device *device_create(struct class *class, struct device *parent,
                     dev_t devt, void *drvdata, const char *fmt, ...)

    功能:创建一个字符设备文件

    参数:

          struct class *class  :类
          struct device *parent:NULL
         dev_t devt  :设备号
         void *drvdata  :null、
         const char *fmt  :名字

    返回:

        struct device *

    下面是源码解析:

    struct device *device_create(struct class *class, struct device *parent,
    			     dev_t devt, void *drvdata, const char *fmt, ...)
    {
    	va_list vargs;
    	struct device *dev;
    
    	va_start(vargs, fmt);
    	dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
    	va_end(vargs);
    	return dev;
    }

    device_create_vargs(class, parent, devt, drvdata, fmt, vargs)解析如下:

    struct device *device_create_vargs(struct class *class, struct device *parent,
    				   dev_t devt, void *drvdata, const char *fmt,
    				   va_list args)
    {
    	return device_create_groups_vargs(class, parent, devt, drvdata, NULL,
    					  fmt, args);
    }
    现在就不继续往下跟了,大家可以继续往下跟;


    下面是一个实例:

    hello.c

    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
    
    static int major = 250;
    static int minor=0;
    static dev_t devno;
    static struct class *cls;
    static struct device *test_device;
    
    static int hello_open (struct inode *inode, struct file *filep)
    {
    	printk("hello_open \n");
    	return 0;
    }
    static struct file_operations hello_ops=
    {
    	.open = hello_open,
    };
    
    static int hello_init(void)
    {
    	int ret;	
    	printk("hello_init \n");
    
    
    	devno = MKDEV(major,minor);
    	ret = register_chrdev(major,"hello",&hello_ops);
    
    	cls = class_create(THIS_MODULE, "myclass");
    	if(IS_ERR(cls))
    	{
    		unregister_chrdev(major,"hello");
    		return -EBUSY;
    	}
    	test_device = device_create(cls,NULL,devno,NULL,"hello");//mknod /dev/hello
    	if(IS_ERR(test_device))
    	{
    		class_destroy(cls);
    		unregister_chrdev(major,"hello");
    		return -EBUSY;
    	}	
    	return 0;
    }
    static void hello_exit(void)
    {
    	device_destroy(cls,devno);
    	class_destroy(cls);	
    	unregister_chrdev(major,"hello");
    	printk("hello_exit \n");
    }
    MODULE_LICENSE("GPL");
    module_init(hello_init);
    module_exit(hello_exit);
    

    test.c

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    
    
    main()
    {
    	int fd;
    
    
    	fd = open("/dev/hello",O_RDWR);
    	if(fd<0)
    	{
    		perror("open fail \n");
    		return ;
    	}
    
    
    	close(fd);
    }

    makefile

    ifneq  ($(KERNELRELEASE),)
    obj-m:=hello.o
    $(info "2nd")
    else
    KDIR := /lib/modules/$(shell uname -r)/build
    PWD:=$(shell pwd)
    all:
    	$(info "1st")
    	make -C $(KDIR) M=$(PWD) modules
    clean:
    	rm -f *.ko *.o *.symvers *.mod.c *.mod.o *.order
    endif

    下面可以看几个class几个名字的对应关系:






    展开全文
  • Linux字符设备驱动剖析

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

    千次阅读 2016-03-20 11:09:13
    文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次、组成框架和交互、如何编写驱动设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解。
  • 6.1 linux字符设备驱动结构 6.1.1 cdev结构体 cdev结构体描述一个字符设备 struct cdev { struct kobject kobj; //内嵌的kobject对象 struct module *owner; //所属模块 const struct file_operations *...
  • Linux字符设备驱动结构 1.1 cdev结构体  在Linux2.6 内核中,使用cdev结构体来描述一个字符设备,cdev结构体的定义如下: struct cdev {  struct kobject kobj;  struct module *owner; /*通常为THIS_...
  • Linux字符设备驱动

    千次阅读 2017-08-27 18:36:35
    1. Linux设备类型Linux内核中的设备可分为三类:字符设备、块设备和网络设备字符设备(Character device):适合面向字符的数据交换,因其数据传输量较低。对这种设备的读写是按字符进行的,而且这些字符是连续...
  • 1、字符设备读写过程     2、字符设备打开过程   ...3、设备节点创建过程 ...4、字符设备驱动注册过程 ...5、字符设备驱动程序中重要的三个数据结构file_operations、inode、file关系: ...
  • 一、嵌入式linux字符设备驱动框架 写应用程序的人 不应该去看电路图,但是如何操作硬件呢:调用驱动程序里的open,read,write等来实现。 C库里实现了 open 、read、write 调用open等:swi val—引发一个异常中断,...
  • Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码。因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块。把下面的范例代码拷贝到Linux系统中: memdev.c #include <linux/module.h>...
  • 我自己当初在学习Linux字符设备驱动的时候,也并没有特地去了解其两者之间的区别,尤其是在两种驱动设备注册的时候,没有意识到其不同之处,导致后来在项目中出现了很严重的问题,但却迟迟到找不到解决方案。...
  • 嵌入式Linux字符设备驱动LED驱动编写

    千次阅读 2016-06-01 21:50:16
    嵌入式Linux字符设备驱动LED驱动编写 标签: linux内核 2015-04-30 14:41 105人阅读 评论(0) 收藏 举报  分类:   Linux开发 嵌入式Linux字符设备驱动LED驱动编写嵌入式Linux字符...
  • LINUX字符设备驱动总结

    千次阅读 2013-12-16 15:05:08
    Linux字符设备驱动 总结
  • Linux字符设备驱动 用到的结构体cdev: 既然要编写字符设备驱动,根据编程思想,需要把字符设备给抽象出来, 在Linux2.6内核中,使用cdev结构体描述一个字符设备 struct cdev{ struct koject kobj;/*...
  • Linux字符设备驱动注册三种方法以及内核分析

    千次阅读 多人点赞 2018-10-07 15:59:38
    Linux驱动是用户访问底层硬件的桥梁,驱动有可以简单分成三类:字符设备、块设备、网络设备。其中最多的是字符设备,其中字符设备的注册方法主要有三种:杂项设备注册、早期字符设备注册、标准字符设备注册。以及...
  • 整理--Linux字符设备驱动开发基础

    千次阅读 2017-01-25 13:42:05
    知识整理–Linux字符设备驱动开发基础 linux驱动:封装对底层硬件的操作,向上层应用提供操作接口
  • Linux--字符设备驱动结构框图

    千次阅读 2016-02-01 21:30:47
    其中cdev结构体用于描述一个字符设备,其定义如下: struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; //设备号 unsigned ...
  • Linux驱动开发学习--字符设备驱动结构(三) http://hi.baidu.com/xdyang1986/blog/item/d3668155066c615f564e00b5.html<br />相关文章:•转一篇顶嵌嵌入式培训的文章:Lin...•Linux设备驱动开发详解--...
  • Linux字符设备驱动程序编写基本流程

    千次阅读 2011-07-06 09:10:12
    linux 字符设备 驱动程序
  • linux字符设备驱动解析

    千次阅读 2015-12-14 21:49:10
    一、linux系统将设备分为3类:字符设备、块设备、网络设备。使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流...
  • 下面开始学习linux字符设备驱动,也是linux驱动中最简单的驱动模块。 在内存中虚拟出一段空间作为字符设备,并为之编写些列的驱动程序。 字符设备驱动cdev中用到的两个重要的结构体如下,现补充下基本知识 一...
  • linux字符设备驱动开发模板及Makefile

    千次阅读 2013-04-18 09:14:11
    linux2.6字符设备驱动开发模板 #include #include #include #include #include //=======================字符设备驱动模板开始 ===========================// #define CHAR_DEV_DEVICE_NAME "char_dev" // ...
  • linux PCI驱动调用字符设备驱动方式

    千次阅读 2017-11-04 21:26:16
    上一篇文章写了字符设备驱动的基本结构及访问方式,在实际...首先介绍下PCI驱动结构://PCI设备id描述结构:这里有两个参数 第一个是VendorID,第二个是DeviceID(在linux Terminal中输入 lspci -vmm可以看到设备信息)
  • Linux字符设备驱动(三)

    千次阅读 2011-05-11 17:39:00
    字符设备编程篇通过程序来体验字符设备驱动编程的过程在Linux系统中,字符设备驱动由如下几个部分组成:(1)字符设备驱动模块加载与卸载函数(2)字符设备驱动的file_operations结构体中成员函数file_operations结构体...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 84,368
精华内容 33,747
关键字:

linux字符设备驱动结构

linux 订阅