linux设备

来源:互联网 发布:淘宝运营托管公司 编辑:程序博客网 时间:2024/05/18 17:04
【字符设备和块设备的区别】
1. 传输速度大小:字符设备是一个字节一个字节传输的,传输速度慢;块设备是以块为单位进行操作的,传输速度快。
2. 响应速度:字符设备响应速度快,块设备必须经过系统的快速缓冲,所以响应速度慢。
3. 访问顺序:字符设备只能按顺序依次访问设备,块设备则可以随机访问设备。
字符设备和块设备之间没有明显的界限,它们之间仅限于驱动与内核的接口不同。

【字符设备驱动模型】






【mmap】
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset ) 
内存映射函数mmap, 负责把文件内容映射到 进程的虚拟内存空间, 通过对这段内存的读取 和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。





addr: 指定映射的起始地址, 通常设为NULL, 由系统指定。 
length: 映射到内存的文件长度。 
prot: 映射区的保护方式, 可以是: 
PROT_EXEC: 映射区可被执行
PROT_READ: 映射区可被读取
PROT_WRITE: 映射区可被写入
flags: 映射区的特性, 可以是:
vMAP_SHARED: 写入映射区的数据会复制回文件, 且允许其他映射该文件的进程共享。 
vMAP_PRIVATE: 对映射区的写入操作会产生一个映射区的复制(copy-on-write), 对此区域所做的修改不会写回 原文件。
fd: 由open返回的文件描述符, 代表要映射的 文件。 
offset: 以文件开始处的偏移量, 必须是分页大小的整数倍, 通常为0, 表示从文件头开始映射

解除映射 :int munmap(void *start,size_t length) 
功能:   取消参数start所指向的映射内存,参数length表示欲取消的内存大小。 
返回值: 解除成功返回0,否则返回-1,错误原因存于 errno中。

【内代码阅读 】
    1. vi + ctags
    2. sourceinside
    3. UE(对代码熟悉,grep和find)
   
【字符设备驱动 】
    要点:驱动框架
   
    字符设备驱动实现步骤:
    1. 定义结构体变量/分配内存(实例化)
       例:
       struct cdev char_demo;
       
    2. 初始化
       /*
        * @brief 初始化cdev结构体变量
        * @param[in|out] cdev 要初始化的字符设备结构体变量
        * @param[in] fops 装载到cdev.ops
        */
       void cdev_init(struct cdev *cdev, const struct file_operations *fops);
   
    3. 添加到内核
/*
 * @brief 分配设备编号
 * @param[in] first 起始设备编号
 * @param[in] count 分配编号数量
 * @param[in] name 设备名称(在/proc/devices文件中可见)
 * @return =0 分配成功
 * <0 错误码
*/
int register_chrdev_region(dev_t first, unsigned int count, char *name);
/*
        * @brief 添加cdev结构体变量到内核
        * @param[in] cdev 要添加到内核的结构体变量
        * @param[in] dev 设备号,就是设备在字符设备数组中的位置,分主设备号和次设备号
        * @param[in] count 添加设备的数量,一般为1
        * @return 0 - 表示添加成功 < 0 错误码
        */
       int cdev_add(struct cdev *p, dev_t dev, unsigned count);
   
字符设备类讲解:
struct cdev {
        struct kobject kobj; // 内核对象, 用于表示在内核中使用
        struct module *owner; // 标识字符设备属于哪一个模块(模块计数)
        const struct file_operations *ops; // 字符设备操作描述
        struct list_head list; // 内核内部使用的链表
        dev_t dev; // 字符设备编号(字符设备在内核中的位置)
        unsigned int count; // 设备数量(1)
   };
字符设备操作
struct file_operations {
 struct module *owner; // 可以根据file_operations的使用迅速的找到模块
 int open(file_name, o_flags)
 /*
 * inode 对应文件节点
 * filp 描述打开文件状态
 * 打开状态 0 - 打开成功, < 0 错误码
 int (*open) (struct inode *inode, struct file *filp);
 
 loff_t llseek(fd, offset, pos);
 loff_t (*llseek) (struct file *, loff_t, int); // 定位,一般不用于字符设备(流设备)
 
 ssize_t read(fd, recv_buf, size)
 /*
 * filp 对应应用层的fd,用于标识操作的是哪个文件
 * buf 应用程序空间的buf
 * size_t buf的大小
 * offset 用于定位,不用于流设备
 * 返回值 读取内容大小
 ssize_t (*read) (struct file *filp, char __user *buf, size_t, loff_t *offset);
 // 读操作(相对于应用程序)
 
 ssize_t write(fd, sent_buf, size) // 写操作(相对于应用程序)
 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
 / *
 * 给驱动发送命令
 * cmd 发给驱动的命令
 * arg 命令的参数
 */
 long ioctl(fd, unsigned int cmd, unsigned long arg);
 long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long arg);
 
 int (*mmap) (struct file *, struct vm_area_struct *); // 把内核的物理内存映射到应用程序地址空间
 
 void close(fd);
 /*
 * inode 标识文件(跟文件名对应)
 * filp 对应fd
 * 关闭状态 0 - 关闭成功 <0 返回错误码
 int (*release) (struct inode *inode, struct file *filp);
};
【字符设备驱动原理 】
    什么是字符设备驱动?
    答:告诉内核字符设备如何使用?
   
    写驱动是实现什么?
    答: 按照操作系统的框架(struct cdev)描述字符设备(设备如何使用?)
   
    如何实现字符设备驱动?
    答:
    1. 定义对象(定义struct cdev结构体变量/给struct cdev结构体变量分配内存空间)
    2. 初始化对象(struct cdev结构体变量)
    3. 添加内核中(让内核知道字符设备是什么样的字符设备?)
   
    字符设备驱动实现步骤:
    1. 定义结构体变量
       struct cdev cdev; (全局)
    2. 实现cdev操作(struct file_operations)
       告诉操作系统cdev如何使用?(调用cdev中实现的函数)
       read函数实现,需要用到:
       copy_to_user
       write函数实现,需用用到:
       copy_from_user
       
    模块初始化中:
    3. 申请设备号(标识一个字符设备)
       register_chrdev_region
    4. 添加字符设备到内核
       cdev_add
       
     模块退出:
    5. 删除字符设备
       cdev_del
       unregister_chrdev_region
【字符设备驱动测试步骤】
    1. 添加字符设备(cdev实现)到内核
       sudo insmod char_dev.ko
       测试加载成功:
       cat /proc/devices 看设备号是否申请成功
    2. 新建设备文件结点
       sudo mknod /dev/chardev c 250(主设备号) 0(次设备号)
    3. 测试打开、读、关闭
       cat /dev/chardev
       看驱动打印信息:
       dmesg | tail
    4. 测试写
       echo "hello\n" > /dev/chardev
       看驱动打印信息:
       dmesg | tail

【学习总结】

字符设备驱动总体框架:

1. 合成获取的设备号

2. 分配设备号

3. 注册设备

4. 卸载驱动

 

XXX_init(void)中合成和分配设备号:

1. 合成设备号:MKDEV(major, minor);

2. 分配设备号:如果静态分配,则register_chrdev_region;

    如果动态分配,则alloc_chrdev_region,并且利用MAJOR(dev_t xxx)分离出主设备号 

3. 调用XXX——setup_cdev(void)注册设备函数

 

XXX_setup_cdev(void)中注册设备:

1. 合成设备号:MKDEV(major,minor);

2. 分配空间: cdev_alloc(可选);

3. 初始化cdevcdev_init(...);

4. 填充所属者:dev.cdev.owner = THIS_MODULE;

5. 添加到内核:cdev_add(...);

 

XXX_exit卸载函数中的相关操作:

1. 删除cdev结构体:cdev_DEL(...);

2. 卸载设备号: unregister_chrdev_region(...);

 

填充file_operations,用到什么函数,填充什么函数。

 

实现file_operations中所填充的函数。

 

-----------------------------------设备号相关操作-----------------------------------

/*

通常而言,主设备号标识设备对应的驱动,次设备号由内核使用,用于正确确定设备文件所指的设备文件所指的设备,

我们可通过次设备号获得指向内核设备的直接指针,也可将次设备号当作设备本地数组的引索。

 

同一类设备使用相同备号,不同类的设备使用不同的主设备号,用次设备号来描述使用该驱动的设备的序号,

序号一般从0开始 ,在调用cdev_add()函数向系统注册字符设备之前,该首先分配设备号

可通过命令:cat /proc/devices 来查看系统使用的设备号

*/

MAJOR(dev_t dev); //将设备编号转换为主设备号 ,dev_t  32 位的数, 12 位用作主编号, 20位用作次编号.

MINOR(dev_t dev); //将设备编号转换为次设备号

MKDEV(int major, int minor)//将主设备号转换为设备编号,minor一般取0

                        可查看Documentation/device.txt文件确定可用的设备号

int register_chrdev_region(dev_t first,unsigned int count, char *name)

                  //分配从first开始count个设备编号,first为预先设定的设备号,

                 成功返回0,失败返回负的错误码,name 是应当连接到这个编号范围的

                //设备的名子; 它会出现在 /proc/devices  sysfs .

                                                                    

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

                //动态分配设备号, firstminor通常为0,分配的结果保存在第一个参数里

                                                                                           

void unregister_chrdev_region(dev_t first, unsigned int count) //卸载设备号

//驱动程序需要将设备编号和内部函数连接起来,这些内部函数用来实现设备的操作

//一般采用一些方式分配设备号:

if(xxx_major)

{

       xxx_devno=MKDEV(xxx_major,xxx_minor); //将主设备号和此设备号转换成设备编号

       result=register_chrdev_region(xxx_devno,1,DEVICE_NAME);

                        //在指定设备主次设备号的情况下,通过这种方式分配设备编号

}

else

{

    result=alloc_chrdev_region(&xxx_devno,0,1,DEVICE_NAME);

                      //如果没有指定主次设备号,则采用这种方式进行动态分配主

    xxx_major=MAJOR(led_devno);

}

 

--------------------------------file_operations结构----------------------------------

struct file_operations { //文件操作

struct module *owner;

 //它并不是一个操作,它指向拥有该结构的模块的指针,内核使用这个字段以避免在模块操作正在被使用时卸载该模块,

   几乎所有的情况下,该成员被初始化为

 //THIS_MODULE,它是定义在<linux/module.h>中的一个宏

                        

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

        //llseek用来修改文件的当前读写位置,并将新位置(正的)作为返回值返回,

   参数loff_t是一个长偏移量,即使在32位平台上也至少

        //占用64位的数据宽度,出错返回负的返回值,如果这个函数指针为NULL

   seek的调用将会以某种不可预期的方式修改file结构中的位置计数器

                                                

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

       //用来从设备读取数据,该函数指针被赋为NULL时,将导致read系统调用出错并返回-EINVAL

                 函数返回非负值表示成功读取的字节数

       //注意更新文件的位置f_pos

       //如果返回值传输的字节数小于count,则应该重新传输数据

       //返回0表示已经到达文件尾

       //如果数据暂时没有到达则应该阻塞

                                                                   

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

 //向设备发送数据,如果没有这个函数,将导致write系统调用出错并返回-EINVAL,函数返回非负值表示成功写入的字节数

      //注意更新文件的位置f_pos

                                                                                           

 ssize_t (*aio_read) (struct kiocb *iocb, const struct iovec *iov,unsigned long nr_segs, loff_t pos);

//初始化一个异步的读取操作---即在函数返回之前可能不会完成的读取操作,如果该方法为NULL,所有的操

                                                                                                      //作将通过read(同步)处理

                                                                                  

 ssize_t (*aio_write) (struct kiocb *iocb, const struct iovec *iov,unsigned long nr_segs, loff_t pos);//初始化一个异步的写入操作

 

 

 unsigned int (*poll) (struct file *filp, poll_table *wait);

  //poll方法是pollepollselect这三个系统调用的后端实现,这三个系统调用可用查询某个或多个文件描述符上读写是否阻塞,poll方法应该返回一位掩码,用来指定非阻塞的读取或写入是否可能,并且也会向内核提供将调用进程至于休眠状态直至I/O变可能时的信息,如果驱动程序将poll方法定义为NULL,则设备被认为即可读也可写,并且不会阻塞

                                                                

 int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); //系统调用ioctl提供了一钟执行设备特定命令的方法(如格式化磁盘,这个既不是读操作也不是写操作),另外,内核 还能识别一部分ioctl命令,而不必调用fops表中的ioctl,如果设备不提供ioctl入口点,对于任何内核未预先定义的请求,ioctl系统调用将返回错误

                                                                                             

 long (*unlocked_ioctl) (struct file *filp, unsigned int ioctl, unsigned long arg);

//不使用BLK的文件系统,将使用此种函数指针代替ioctl

 long (*compat_ioctl) (struct file *file, unsigned cmd, unsigned long arg);

//64位系统上,32位的ioctl调用,将调用此函数指针代替

 

 int (*mmap) (struct file *file, struct vm_area_struct *vma);

//mmap用于请求将设备内存映射到进程空间,如果设备没有实现这个方法,那么mmap系统调用将返回-ENODEV

 

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

//尽管这是对设备文件的第一个操作,然而却并不要求驱动程序一定要声明一个相应的方法,如果这个入口为NULL,设备的打开操作永远成功,但系统不会通知驱动程序

 

 int (*flush) (struct file *filp, fl_owner_t id);

//flush操作的调用发生在进程关闭设备文件描述符副本的时候,它应该执行(并等待)设备上尚未完结的操作,目前,flush仅仅用于少数几个驱动程序 ,如果flush被设置为NULL,内核将简单低忽略用于的应用程序请求

 

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

//file结构被释放时即关闭设备文件,将调用这个操作,与open相仿,也可以将release设置为NULL

 

 int (*fsync) (struct file *filp, struct dentry *dentry, int datasync);

//该方法是fsync系统调用的后端实现,用户调用它来刷新待处理的数据,如果驱动程序没有实现这一方法,fsync系统调用将返回-EINVAL

 

 int (*aio_fsync) (struct kiocb *iocb, int datasync); //这是fsync方法的异步版本

 

 int (*fasync) (int fd, struct file *filp, int mode);

//这个操作用来通知设备其FASYNC标志发生了变化,如果设备不支持异步通知,该字段可以使NULL

 

 int (*lock) (struct file *filp, int cmd, struct file_lock *fl); //lock方法用于实现文件锁定,锁定是常规文件不可缺少的特性,但设备驱动程序几乎不会实现这个方法

 

 unsigned long (*get_unmapped_area)(struct file *file,unsigned long addr, unsigned long len,unsigned long pgoff,unsigned long flags);

                                                                                                                 //该方法的目的是在进程的地址空间找到一个合适的位置,以便将底层设备中的内存映射到该位置,大部分驱动程序可设置改方法为  NULL  

 int (*check_flags)(int); //该方法允许模块检查传递给fcntl调用的标志

 

 

 ssize_t (*splice_write)(struct pipe_inode_info *pipe, struct file *out,loff_t *ppos, size_t len, unsigned int flags); //VFS调用,将管道数据粘接到文件

 

 ssize_t (*splice_read)(struct file *in, loff_t *ppos,struct pipe_inode_info *pipe, size_t len,unsigned int flags); //VFS调用,将文件数据粘接到管道

};

struct file_operations xxx_fops={

                                .owner=THIS_MODULE,

                                .open=xxx_open,

                                .read=xxx_read,

                                .write=xxx_write,

                                .ioctl=xxx_ioctl,

                                .poll=xxx_poll,

                                .release=xxx_close,

                                ......

                               };

-----------------------------file 结构---------------------------------------------

/*

 *文件结构代表一个打开的文件(文件描述符). (它不特定给设备驱动; 系统中每个打开的文件有一个关联的 struct file 在内核空间). 它由内核在 open 时创建, 并传递 给在文件上操作的任何函数, 直到最后的关闭. 在文件的所有实例都关闭后,内核释放这个数据结构

.*/

struct file { //file结构,指向该结构的指针为filep/file(文件指针)

 /*

  * fu_list becomes invalid after file_free is called and queued via

  * fu_rcuhead for RCU freeing

  */

 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;

//与文件相关操作,内核在执行open操作时对这个指针赋值,以后需要处理这些操作时就读取这个指针,filep->f_op中的值决不会为方便引用而保存起来 ,也就是说,我们可以在任何时候修改文件的关联操作,在返回给调用者之后,新的操作方法就会立即生效

 spinlock_t  f_lock; /* f_ep_links, f_flags, no IRQ */

 atomic_long_t   f_count;

 unsigned int f_flags;

 //文件标志,如O_RDONLY O_NONBLOCK O_SYNC,为了检查用户请求的是否是非阻塞式的操作,

            //驱动程序需要检查O_NONBLOCK标志,而其他标志很少用到,所有的标志在<linux/fcntl.h>

 

 fmode_t   f_mode;

 //文件模式,它通过FMODE_READFMODE_WRITE位来标识文件是否可读或可写或可读写,驱动程序无需为此而作额外的判断

 

 loff_t  f_pos;

 //当前的读/写位置,loff_t是一个64位数(gcclong long定义),如果驱动程序需要知道文件中的当前位置,可以读取这个值,但不需要修改它,read/write

 //会使用它们接收到最后的那个指针参数来更新这一位置,而不是直接对filep->f_pos进行操作

 

 struct fown_struct f_owner;//用来保存异步通知的属主进程的进程的ID号,目的是为了让内核知道应该通知哪一个进程

 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;

 //open系统调用在调用驱动程序的open方法之前将这个指针设置为NULLprivate_data在跨系统调用时保存状态信息是非常有用的资源,我们大部分示例都使用它 ,struct xxx_dev *xxx_dev; dev=container_of(inode->i_cdev,struct xxx_dev,cdev) //xxx_dev是我们自己定义的设备私有结构体,其中包含 struct cdev cdev成员,这样今后就可以方便对该指针的访问了,container_of是通过结构体内部成员的地址来获取整个结构体的地址

#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

};

--------------------------------------------------------------------------------------

---------------------------------inode结构---------------------------------------------

//inode 结构由内核在内部用来表示文件. 因此, 它和代表打开文件描述符的文件结构是不同的. 可能有代表单个文件的多个打开的文件描述符的file结构, 但是它们都指向一个单个 inode 结构

struct inode { //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;

 gid_t   i_gid;

 dev_t   i_rdev; //对表示设备文件的inode结构,该字段包含了真正的设备编号

 u64   i_version;

 loff_t  i_size;

#ifdef __NEED_I_SIZE_ORDERED

 seqcount_t  i_size_seqcount;

#endif

 struct timespec   i_atime;

 struct timespec   i_mtime;

 struct timespec   i_ctime;

 unsigned int  i_blkbits;

 blkcnt_t  i_blocks;

 unsigned short i_bytes;

 umode_t   i_mode;

 spinlock_t  i_lock;  /* i_blocks, i_bytes, maybe i_size */

 struct mutex  i_mutex;

 struct rw_semaphore  i_alloc_sem;

 const struct inode_operations  *i_op;

 const struct file_operations *i_fop; /* former ->i_op->default_file_ops */

 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;//若是块设备,其对应的是block_device结构体指针

  struct cdev  *i_cdev; //struct cdev 是表示字符设备的内核的内部结构,当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针(指向我们初始化并注册的struct cdev结构)

 };

 

inode中获得主设备号和次设备号:

unsigned int iminor(struct inode *inode);

unsigned int imajor(struct inode *inode);

--------------------------------------------------------------------------------------

-----------------------------------------字符设备注册-----------------------------------

struct xxx_dev{ //描述设备结构体

                 struct cdev cdev; //字符设备cdev结构体

                 unsigned char button_value; //状态标志,=1表示有按键按下

                 struct semaphore sem; //信号量

                 wait_queue_head_t outq;

                 struct fasync_struct *async_quene;

                };

struct xxx_dev *xxxx_dev;

xxxx_dev=kmalloc(sizeof(struct xxx_dev), GFP_KERNEL); //为设备描述结构体分配内存

//如果直接定义了该结构则可用下面函数来分配该结构:

struct cdev *cdev_alloc(void); //struct cdev分配一个结构体

if(!xxxx_dev)

{

   ret=-ENOMEM;

   goto fail_malloc;

}

  

memset(xxxx_dev,0,sizeof(struct xxx_dev)); //将分配的内存清零

void cdev_init(struct cdev *cdev, struct file_operations *fops) //初始化已分配的设备结构

cdev.owner=THIS_MODULE;

cdev.ops=&file_operations xxx

int cdev_add(struct cdev *dev, dev_t num, unsigned int count)

 //注册设备,num为设备号,count是应该和该设备关联的设备编号的数量,通常取1

void cdev_del(struct cdev *dev) //卸载设备

//早期的方法:

int register_chrdev(unsigned int major, const char *name,struct file_operations *fops) 

//注册字符设备

int unregister_chrdev(unsigned int major, const char *name) //卸载设备

/*********************************end********************************************/

/****************************用户空间和内核空间的数据传输**********************************/

unsigned long copy_to_user(void __user *to, const void *from, unsigned long count);

//返回值是还需要拷贝的内存数量

unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);

 //返回值是还需要拷贝的内存数量

//如果要复制的内存是简单类型,如 charintlong等,则可以使用下面简单函数:

int val; //内核空间整型变量

get_user(val,(int *)arg); //用户空间到内核空间传输数据,arg是用户空间的地址

put_user(val,(int *)arg); //内核空间到用户空间传输数据,arg是用户空间的地址

/****************************end*******************************************/

【学习代码】

【驱动代码】 char_dev.c

 

#include <linux/init.h>   // 模块头文件

#include <linux/module.h>

 

#include <linux/cdev.h> // 字符设备头文件

#include <linux/fs.h>

#include <linux/uaccess.h> // copy_to_user/copy_from_user

 

#include <linux/ioctl.h> // ioctl

 

#include <linux/device.h> // 导出设备信息到sysfs

 

#include "char_dev.h"

// 1. 创建字符设备对象(定义结构体变量)

// struct cdev char_demo; 静态定义

struct cdev *char_demo;

struct class * char_demo_class;

struct device *char_demo_device;

// 0-表示需用动态分配设备号 0 - 表示静态分配设备号

int major = 0;

int minor = 0;

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

{

 printk("char open\n");

 return 0;

}

ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 /*

  * @brief 拷贝数据到应用程序空间

  * @param[out] to 应用程序空间buf,要拷贝到的地方

  * @param[in] from 内核空间buf,从哪个地方拷贝数据

  * @param[in] n 拷贝数据长度

  * @return 未成功拷贝的数据数量, 一般返回0

  */

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

 ret = copy_to_user(buf, "char dev hello\n", 15);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = 15;

 }

 

 return ret;

}

ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)

{

 int ret = 0;

 char str[20];

 

 /*

  * @brief 拷贝数据从应用程序空间

  * @param[out] to 内核空间buf,要拷贝到的地方

  * @param[in] from 应用空间buf,从哪个地方拷贝数据

  * @param[in] n 拷贝数据长度

  * @return 未成功拷贝的数据数量, 一般返回0

  */

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

 

 ret = copy_from_user(str, buf, size);

 if (ret) {

  ret = -ENOMEM;

 } else {

  ret = size;

  printk("%s\n", str);

 }

 

 return ret;

}

long char_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

{

 long ret = 0;

 

 switch (cmd) {

  case CMD_DEMO0:

   printk("CMD_DEMO0\n");

   break;

  

  case CMD_DEMO1:

   printk("CMD_DEMO1\n");

   break;

  

  default:

   ret = -ENOTTY;

   break;

 }

 

 return ret;

}

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

{

 printk("char release\n");

 return 0;

}

struct file_operations fops = {

 .owner = THIS_MODULE, // 当前模块

 .open = char_open,

 .read = char_read,

 .write = char_write,

 .unlocked_ioctl = char_ioctl,

 .release = char_release,

};

 

int __init char_init(void)

{

 int ret = 0;

 dev_t devno;

 

 // 给设备分配位置

 if (major != 0) {

  // 静态分配设备号

  // 找设备号的方法:Documention/devices

  devno = MKDEV(major, 0);

  ret = register_chrdev_region(devno, 1, "char demo");

  if (ret) {

   goto register_chrdev_region_err;

  }

 

 } else {

 

  /*

  * @brief 动态分配设备号

  * @param[out] dev 返回分配的设备号

  * @param[in] baseminor 第一个次设备号

  * @param[in] count 分配设备号的数量

  * @param[in] name 主设备号的名字

  * @return 0 分配成功

  * < 0 错误码

  *

  int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,

   const char *name)

  */

   ret = alloc_chrdev_region(&devno, minor, 1, "char demo");

   if (ret) {

    goto register_chrdev_region_err;

   }

   major = MAJOR(devno);

 }

 

 // 1. 创建字符设备对象(动态分配)

 char_demo = cdev_alloc();

 if (NULL == char_demo) {

  ret = -ENOMEM;

  goto cdev_alloc_err;

 }

 

 // 2. 初始化字符设备对象

 char_demo->owner = THIS_MODULE;

 char_demo->ops = &fops;

 // cdev_init(&char_demo, &fops);

 

 // 3. 添加字符设备到内核

 // ret = cdev_add(&char_demo, devno, 1);

 ret = cdev_add(char_demo, devno, 1);

 if (ret) {

  goto cdev_add_err;

 }

 

 // 1. 创建设备类

 char_demo_class = class_create(THIS_MODULE, "char_demo");

 if (IS_ERR(char_demo_class)) {

  ret = PTR_ERR(char_demo_class);

  goto class_create_err;

 }

 

 // 2. 导出设备信息到应用空间

 char_demo_device = device_create(char_demo_class, NULL, devno, NULL, "chardev");

 if (IS_ERR(char_demo_device)) {

  ret = PTR_ERR(char_demo_class);

  goto device_create_err;

 }

 

 goto register_chrdev_region_err;

 

device_create_err:

 class_destroy(char_demo_class);

 

class_create_err:

 cdev_del(char_demo);

cdev_add_err:

cdev_alloc_err:

 unregister_chrdev_region(devno, 1);

 

register_chrdev_region_err:

 return ret;

}


void __exit char_exit(void)

{

 dev_t devno = MKDEV(major, minor);

 

 device_destroy(char_demo_class, devno);

 class_destroy(char_demo_class);

 // cdev_del(&char_demo);

 cdev_del(char_demo);

 unregister_chrdev_region(devno, 1);

}

module_init(char_init);

module_exit(char_exit);

MODULE_LICENSE("GPL");

0 0
原创粉丝点击