7、字符设备系统

一、字符设备驱动的结构

1、cdev 结构体

struct cdev {
                struct kobject kobj;    // 内嵌的Kobject对象
                struct module *owner;  // 所属的模块
                const struct file_operations *ops;  // 文件操作结构体
                struct list_head list;  // 内核维护你的链表
                dev_t dev;  // 设备号,由主次设备号组成
                unsigned int count;
        };

    在这个结构体中,dev_t dev,是设备的设备号,它由32位的,由两个部分,既主设备号和次设备号组成,12位是主设备号,20位次设备号可以用过下面的宏来获得主次设备号:

MAJOR(dev_t dev)   // 获得主设备号

MINOR(dev_t dev)   // 获得次设备号

    如果要将它们合成为一个 dev_t 类型的话:

   MKDEV(int major, int minor);   //获得设备号

2、cdev 设备的操作

    struct cdev*cdev_alloc(void);     // 申请内存

    void cdev_init(struct cdev *cdev, struct file_operation * fops)   // fops 与 cdev 设备绑定

    int cdev_add(struct cdev *cdev,dev_t devt, unsigned num);   // 字符设备的注册

    void cdev_del(struct cdev *cdev)  // 注销

    cdev_init 函数,建立cdev 与文件操作指针的连接,cdev_add 是将字符设备注册到系统,cdev_add 里面的一个参数是 dev_t ,所以之前必须先获取到设备号。 cdev)_del 注销设备,一般是在字符设备卸载的函数中。

    cdev 注册之前,需要获取到设备号:

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

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

    register_chrdev_region 是在已经确定设备号 from 的时候,count 是申请的个数, name :名字,这些名字,最终是会在 /proc/devices 中显示。

    alloc_chrdev_region ,一般是使用这个,因为一般都是不知道设备号的,minor: 申请最小的次设备号,count 个数,name 名字。

    完成设备号的注册,注销的时候是:

    void unregister_chrdev_region(dev_t from,unsigned count);

 

3、字符设备操作流程

   struct cdev *mycdev = NULL;   // 1、定义 cdev 设备

    static  int __init xxx_init(void)   // 入口

    {

        mycdev = cdev_alloc();   // 2、分配地址

        cdev_init(mycdev, &fops);   // 3、初始化,将文件操作结构体绑定

        ret = alloc_cdev_region(&xxx_dev_no, 0, 1 , “mycdev”);   // 4、分配设备号

        mycdev->owner = THIS_MODULE;   //5、其余参数的指定

 

       cdev_add(mycdev, xxx_dev_no, 1);  // 6、注册设备;

    }

   static void __exit  xxx_exit(void)

    {

        unregister_chrdev_region(xxx_dev_no, 1);

        cdev_del(mycdev);

    }

    module_init(xxx_init);

    module_exit(xxx_exit);

二、重要的结构体

1、file_operation 结构体

    file_operation 的成员函数,是字符设备驱动与内核虚拟文件系统的接口,一般,提供了,read、write、ioctl 等常用函数。

struct file_operations {

loff_t(*llseek) (struct file *, loff_t, int);

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

ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);

ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);

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

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

int (*open) (struct inode *, struct file *);

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

};

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

    file : 结构体,将在后续的位置学习, 

    __user : 充当了特殊的注释,说明 buf 是用户空间的内存地址,这里需要注意的是,用户空间的地址与内核空间的地址,是不能直接进行读写的

    count : 要写入 buf 的字节数,

   f_pos : 文件指针的位置,

    成功,则返回写入的字节数,失败则返回 EINVAL

ssize_t(*write) (struct file * filp, const char __user * buf, size_t count, loff_t *fpos);

   __user  : 用户空间的buf

   count ; 写入buf 的字节数

    fpos : 文件指针

而 用户空间与内核空间的数据,只能通过 copy_to_usr 或者 copy_from_usr 完成内核空间与数据空间的交换,

int (*open) (struct inode *, struct file *);

inode : 结构体指针

file : 指针

2、file 结构体

      当打开一个文件的时候,系统会为每个打开的文件,在内核的空间有一个关联的 struct file。她由内核在打开文件的时候,进行创建,当要对这个文件进行其他操作的时候,会将这个结构体 struct file 传递给任何要对这个文件进行操作的函数。file 结构体 里面保存了关于文件的 N 多信息,

struct file {
       union {
        struct list_head    fu_list;
        struct rcu_head     fu_rcuhead;
    } f_u;
    struct path        f_path;
#define f_dentry    f_path.dentry
#define f_vfsmnt    f_path.mnt
    const struct file_operations    *f_op;                      // 文件管理的操作
    spinlock_t        f_lock;  /* f_ep_links, f_flags, no IRQ */
#ifdef CONFIG_SMP
    int            f_sb_list_cpu;
#endif
    atomic_long_t        f_count;
    unsigned int         f_flags;                        // 文件的标志, O_RDONLY,O_NONBLOCK,
    fmode_t            f_mode;                        // 文件的读或者写模式, FMODE_READ,FMODE_WRITE
    loff_t            f_pos;                             // 文件的指针
    struct fown_struct    f_owner;
    const struct cred    *f_cred;
    struct file_ra_state    f_ra;

    u64            f_version;
#ifdef CONFIG_SECURITY
    void            *f_security;
#endif
    /* needed for tty driver, and maybe others */
    void            *private_data;                   // 文件的私有数据

#ifdef CONFIG_EPOLL
    /* Used by fs/eventpoll.c to link all the hooks to this file */
    struct list_head    f_ep_links;
#endif /* #ifdef CONFIG_EPOLL */
    struct address_space    *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
    unsigned long f_mnt_write_state;
#endif
};

    file 结构体里面信息很多,但是常用的较少,打开文件的时候, file 里面填充的信息,我们可以去使用,比如我们可以判定是不是非阻塞的模式:

    if(file->flags & O_NONBLOCK)

            printf(“NONBLOCK N”);

    else

            printf(“BLOCK N”);

    而 file 里面的私有数据 void            *private_data,这个指针,一般上指向了保存自定义设备结构体的地址,当read 或者 write 的时候,再从这个私有的指针,获取到自动的设备结构体。

3、 inode 结构体

      当文件保存在磁盘的时候,inode 结构体 保存了文件访问的属性、属主、大小、访问时间、生成时间、最后修改时间等信息。她是 Linux管理文件系统的最基本的单位。

struct inode {
     umode_t            i_mode;        
    uid_t            i_uid;
    gid_t            i_gid;
     struct mutex        i_mutex;

    dev_t            i_rdev;                       // 文件的设备号
    struct timespec        i_atime;       // access 最后的存取时间
    struct timespec        i_mtime;      // mod 最后的修改时间
    struct timespec        i_ctime;     //  inode 产生时间,create

    union {
        struct pipe_inode_info    *i_pipe;
        struct block_device    *i_bdev;
        struct cdev        *i_cdev;      // 字符设备
    };

    上面的这些信息中,对于编写驱动最为有用的是 dev_t i_rdev; 记录的是文件的设备号,struct cdev *i_cdev;,可以通过:

unsigned int imonir(struct inode * inode)  // 获取次设备号

unsigned int imajor(struct inode * inode)  // 获取主设备号

   也可以通过 stat + 文件的命令,查看 一个文件的状态:

[carlos@localhost 3516c]$ stat gpio.ko
  File: “gpio.ko”
  Size: 7757            Blocks: 16         IO Block: 4096   一般文件
Device: 801h/2049d      Inode: 358613086   Links: 1
Access: (0664/-rw-rw-r--)  Uid: (  566/  carlos)   Gid: (  566/  carlos)
Access: 2016-05-07 15:29:55.000000000 +0800
Modify: 2016-05-06 13:31:14.000000000 +0800
Change: 2016-05-06 13:31:14.000000000 +0800

    其实也是获取了 inode 的信息。

原文地址:https://www.cnblogs.com/qxj511/p/5468899.html