Linux字符设备

时间:2024-05-04 16:33:20

一、linux系统将设备分为3类:字符设备、块设备、网络设备。

  • 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。
  • 块设备:是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。
  • 网络设备是计算机体系结构中必不可少的一部分,处理器如果想与外界通信,通常都会选择网络设备作为通信接口。众所周知,在 OSI(Open Systems
    Interconnection,开放网际互连)中,网络被划分为七个层次,从下到上分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。我们所讲的网络设备也包括两个层次,一层叫做
    MAC(Media Access Control)层,对应于 OSI 的数据链路层;另一层叫做 PHY(Physical Layer)层,对应于物理层。

1.cdev结构体

struct cdev{

  struct kobject kobj;//内嵌的kobj对象

  struct module *owner;//所属模块

  struct file_operations *ops;//文件操作结构体

  struct list_head list;

  dev_t dev;//设备号

  unsigned int count;

};

cdev 结构体的dev_t成员定义了设备号,为32位,其中高12位为主设备号,低20位为次设备号.

MAJOR(dev_t dev)获得主设备号和MINOR(dev_t dev)获得次设备号

MKDEV(int major,int minor)可以生成dev_t

file_operation定义了字符设备提供给文件系统的接口函数

void cedv_init (struct cdev *,struct file_operation *);//初始化cedv的成员,建立cedv和file_operation的连接

struct cdev *cdev_alloc(void);//动态申请一个cdev内存

void cdev_put(struct cdev *p);

int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
 
//cdev_add()函数和 cdev_del()函数分别向系统添加和删除一个 cdev, 完成字符设备的注册和注销

cdev_add()向系统注册字符设备时,先调用register_chrdev_region()或者alloc_chrdev_region()向系统申请设备号

cdev_del()向系统注销字符设备后,unregister_chrdev_region()应该被调用以释放原先申请的设备号

分配和释放设备号

int register_chrdev_region(dev_t from,unsigned count,const char* name);//用已知的设备号

int alloc_chrdev_region(dev_t *dev ,unsigned baseminor,unsigned count,const char *name);//用于设备号未知

void unregister_chrdev_region(dev_t from,unsigned count);//卸载

file_operations结构体

struct file_operation{

struct module *owner;//拥有该模块的指针,一般为this module

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 * ,char user *,size_t,loff_t);//同步写入,成功写入内容,失败负值

int(*readdir)(struct file *,void *,filldir_t);//读取目录,对于设备文件,该字段为空,设备节点不需要实现它

unsigned int(*poll)(struct file *,struct poll_table_struct*);//轮询函数,是否为非阻塞的读入还是写出,当询问条件未触发时,select()和poll()系统调用引起阻塞
int(*ioctl)(struct inode *,struct file *,unsigned long);//执行io控制命令
 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*);
    // 用于请求将设备内存映射到进程地址空间,如果设备驱动未实现此函数,用户进行 mmap()系统调用时将获得-ENODEV 返回值
    int(*open)(struct inode *, struct file*);
    // 打开,驱动程序可以不实现这个函数,在这种情况下,设备的打开操作永远成功。
    int(*flush)(struct file*);
    int(*release)(struct inode *, struct file*);
    // 关闭
    int(*synch)(struct file *, struct dentry *, int datasync);
    // 刷新待处理的数据
    int(*aio_fsync)(struct kiocb *, int datasync);
    // 异步 fsync
    int(*fasync)(int, struct file *, int);
    // 通知设备 FASYNC 标志发生变化
    int(*lock)(struct file *, int, struct file_lock*);
    ssize_t(*readv)(struct file *, const struct iovec *, unsigned long,loff_t*);
    ssize_t(*writev)(struct file *, const struct iovec *, unsigned long,loff_t*);
    // readv 和 writev:分散/聚集型的读写操作
    ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_actor_t,void*);
    // 通常为 NULL
    ssize_t(*sendpage)(struct file *, struct page *, int, size_t,loff_t *, int);
    // 通常为 NULL
    unsigned long(*get_unmapped_area)(struct file *,unsigned long,unsigned long,unsigned long, unsigned long);
    // 在进程地址空间找到一个将底层设备中的内存段映射的位置
    int(*check_flags)(int);
    // 允许模块检查传递给 fcntl(F_SETEL...)调用的标志
    int(*dir_notify)(struct file *filp, unsigned long arg);
    // 仅对文件系统有效,驱动程序不必实现
    int(*flock)(struct file *, int, struct file_lock*);
};

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

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

//user是一个宏,表明其后的指针指向用户空间

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

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