linux内核驱动学习--字符设备驱动程序

linux设备驱动–字符设备驱动程序

简介

字符设备是Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节
流进行读写操作的设备,读写数据是分先后顺序的。常见的字符设备包括鼠标、键盘、显示器、串口等等。当我们执行ls -l /dev的时候,就能看到大量的设备文件,c就是字符设备,b就是块设备,网络设备没有对应的设备文件。编写一个外部模块的字符设备驱动,除了要实现编写一个模块所需要的代码之外,还需要编写作为一个字符设备的代码。

在这里插入图片描述


设备号

对字符设备的访问是通过文件系统内的设备名称进行的。那些名称被称为特殊文件、设备文件,或者简单称之为文件系统树的节点,它们通常位于/dev目录.字符设备驱动程 序的设备文件可通过ls -I命令输出的第一列中的“c"来识别.块设备也出现在/dev下, 但它们由字符“b"标识。

设备编号的内部表达

在内核中,dev_t类型(在<linux/types.h>中定义)用来保存设备编号一包括主设备号和次设备号。在内核的2.6.0版本中,dev_t是一个32位的数,其中的12位用来 表示主设备号,而其余20位用来表示次设备号。当然,我们的代码不应该对设备编号的 组织做任何假定,而应该始终使用<linux/kdev_t.h>中定义的宏。比如、要获得dev_t的主设备号或次设备号,应使用:

MAJOR(dev_t dev);  //主设备号
MINOR(dev_t dev) ; //次设备号

相反,如果需要将主设备号和次设备号转换成dev_t类型,则使用:

MKDEV(int major, int minor);

分配和释放设备编号

在建立一个字符设备之前,我们的驱动程序首先要做的事情就是获得 一个或者多个设备 编号。完成该工作的必要函数是register_chrdev_region,该函数在<linux/fs.h>中声明

  • 静态分配设备号的函数原型
int register_chrdev_region(dev_t first, unsigned int count, char *name); 
参数说明
first要分配的设备编号范围的初始值, 这组连续设备号的起始设备号, 相当于register_chrdev()中主设备号
count连续编号范围, 是这组设备号的大小(也是次设备号的个数)
name编号相关联的设备名称. (/proc/devices); 本组设备的驱动名称

register_chrdev _region的返回值在分配成功时为0。在错误情况下,将返回一个负的错误码, 并且不能使用所请求的编号区域。
适用于:提前明确知道所需要的设备编号

  • 动态分配设备号的函数原型
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); 
参数说明
dev输出型参数,获得一个分配到的设备号
firstminor次设备号的基准,从第几个次设备号开始分配
count次设备号的个数
name驱动的名字
  • 释放设备号的函数原型
void unregister_chrdev_region(dev_t from, unsigned count); 
参数说明
from设备号
count次设备号的个数

重要的数据结构

file_operations结构

Linux使用file_operations结构访问驱动程序的函数,这个结构的每一个成员的名字都对应着一个调用。
用户进程利用在对设备文件进行诸如read/write操作的时候,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数,这是Linux的设备驱动程序工作的基本原理。
字符设备驱动程序经常用到的5种操作
详细的解析可以参考:file_operations 说明

struct file_operations
{
    ssize_t (*read)(struct file *char *, size_t, loff_t *);//从设备同步读取数据
    ssize_t (*write)(struct file *,const char *, size_t, loff_t *);
    int (*ioctl) (struct  inode *,  struct file *, unsigned int,  unsigned long);//执行设备IO控制命令
    int (*open) (struct inode *, struct file *);//打开
    int (*release)(struct inode *, struct file *);//关闭
    ...
};

这个结构定义在<linux/fs.h>中,其中包含了一组函数指针。每个打开的文件(在内部由一个file结构表示)和一组函数关联(通过包含指向一个file_operations结构的f_op字段)。这些操作主要用来实现系统调用,命名为open、read等等。
可以认为文件是一个“对象”,而操作它的函数是“方法”,如果采用面向对象编程的术语来表达就是, 对象声明的动作将作用千其本身。
在file_operations方法中,有许多参数包含有__user字符串,它其实是一种形式的文档而已,表明指针是一个用户空间地址,因此不能被直接引用

owner:它是指向 “拥有" 该结构的模块的指针。 内核使用这个字段以避免在校块的操作正在被使用时卸载该楼块。 儿乎在所有的情况下, 该成员都会被初始化为THIS_MODULE, 它是定义在<linux/module.h>中的一个宏.

file结构

struct file {
	union {
		struct llist_node	fu_llist;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		        f_path;
	struct inode		   *f_inode;	/* cached value */
	const struct file_operations	*f_op;

	/*
	 * Protects f_ep_links, f_flags.
	 * Must not be taken from IRQ context.
	 */
	spinlock_t		        f_lock;
	atomic_long_t		    f_count;
	unsigned int 		    f_flags;
	fmode_t			        f_mode;
	struct mutex		    f_pos_lock;
	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;
	struct list_head	    f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space   *f_mapping;
} __attribute__((aligned(4)));	/* lest something weird decides that 2 is OK */

在<linux/fs.h>中定义的structfile是设备驱动程序所使用的第二个最重要的数据结构。struct file是一个内核结构,它不会出现在用户程序中。
file结构代表一个打开的文件(它并不仅仅限定于设备驱动程序,系统中每个打开的文件在内核空间都有一个对应的file结构)。它由内核在open时创建,井传递给在该 文件上进行操作的所有函数,直到最后的close函数。在文件的所有实例都被关闭之后, 内核会释放这个数据结构。

  • mode_t f_mode
    文件模式。它通过FMODE_READ和FMODE_ERITE位来标识文件是否可读或可写(或可读写)。读者可能会认为要在自己的open或ioctl函数中查看这个字段,以便检查是否拥有读/写访问权限,但由千内核在调用驱动程序的read和write前已经检查了访问权限,所以不必为这两个方法检查权限。在没有获得对应访问权限而打开文件的情况下,对文件的读写操作将被内核拒绝,驱动程序无需为此而作额外的判断。
  • loff_t f_pos
    当前的读/写位置。loff_t是一个64位的数(用gee的术语说就是longlong)。 如果驱动程序需要知道文件中的当前位置,可以读取这个值,但不要去修改它。 readlwri1e会使用它们接收到的最后那个指针参数来更新这一位置,而不是直接对 filp->f_pos进行操作。这一规则的一个例外是llseek方法,该方法的目的本身就是为了修改文件位置。
  • unsigned int f_flags
    文件标志、如O_RDONLY、O_NONBLOCK和O_SYNC。为了检查用户请求的是否是非阻塞式的操作驱动程序需要桧查O_NONBLOCK标志,而其他标志很少用到。注意,检查读/写权限应该查看f_mode而不是f_flags。所有这些标志都定义在<linux/fcntl.h>中。
  • struct file_operations *f_op
    与文件相关的操作。内核在执行open操作时对这个指针赋值,以后需要处理这些操作时就读取这个指针。filp->f_op中的值决不会为方便引用而保存起来;也就是说,我们可以在任何需要的时候修改文件的关联操作,在返回给调用者之后, 新的操作方法就会立即生效。例如,对应千主设备号l(/dev/null、/dev/zero等等) 的open代码根据要打开的次设备号替换filp->f_op中的操作。这种技巧允许相同主设备号下的设备实现多种操作行为,而不会增加系统调用的负担。这种替换文件操作的能力在面向对象编程技术中称为"方法重载"。
  • void *priva七e_data
    open系统调用在调用驱动程序的open方法前将这个指针置为NULL。驱动程序可 以将这个字段用千任何目的或者忽峈这个字段.驱动程序可以用这个字段指向已分配的数据,但是一定要在内核销毁file结构前在release方法中释放内存。 private_data是跨系统调用时保存状态信息的非常有用的资源,我们的大部分示 例都使用了它。
  • struct dentry *f_dentry
    件对应的目录项(dentry)结构。除了用filp->f_dentry->d_inode的方式来 访问索引节点结构之外,设备驱动程序的开发者们一般无需关心dentry结构。

inode结构

struct inode {
	umode_t			i_mode;
	unsigned short		i_opflags;
	kuid_t			i_uid;
	kgid_t			i_gid;
	unsigned int		i_flags;

#ifdef CONFIG_FS_POSIX_ACL
	struct posix_acl	*i_acl;
	struct posix_acl	*i_default_acl;
#endif

	const struct inode_operations	*i_op;
	struct super_block	*i_sb;
	struct address_space	*i_mapping;

#ifdef CONFIG_SECURITY
	void			*i_security;
#endif

	/* Stat data, not accessed from path walking */
	unsigned long		i_ino;
	/*
	 * Filesystems may only read i_nlink directly.  They shall use the
	 * following functions for modification:
	 *
	 *    (set|clear|inc|drop)_nlink
	 *    inode_(inc|dec)_link_count
	 */
	union {
		const unsigned int i_nlink;
		unsigned int __i_nlink;
	};
	dev_t			    i_rdev;
	loff_t			    i_size;
	struct timespec		i_atime;
	struct timespec		i_mtime;
	struct timespec		i_ctime;
	spinlock_t		    i_lock;	/* i_blocks, i_bytes, maybe i_size */
	unsigned short      i_bytes;
	unsigned int		i_blkbits;
	blkcnt_t		    i_blocks;

#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t		i_size_seqcount;
#endif

	/* Misc */
	unsigned long		i_state;
	struct mutex		i_mutex;

	unsigned long		dirtied_when;	/* jiffies of first dirtying */
	unsigned long		dirtied_time_when;

	struct hlist_node	i_hash;
	struct list_head	i_wb_list;	/* backing dev IO list */
	struct list_head	i_lru;		/* inode LRU list */
	struct list_head	i_sb_list;
	union {
		struct hlist_head	i_dentry;
		struct rcu_head		i_rcu;
	};
	u64			    i_version;
	atomic_t		i_count;
	atomic_t		i_dio_count;
	atomic_t		i_writecount;
#ifdef CONFIG_IMA
	atomic_t		i_readcount; /* struct files open RO */
#endif
	const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
	struct file_lock_context	    *i_flctx;
	struct address_space	         i_data;
	struct list_head	             i_devices;
	union {
		struct pipe_inode_info	*i_pipe;
		struct block_device	    *i_bdev;
		struct cdev		        *i_cdev;
	};

	__u32			i_generation;

#ifdef CONFIG_FSNOTIFY
	__u32			    i_fsnotify_mask; /* all events this inode cares about */
	struct hlist_head	i_fsnotify_marks;
#endif

	void			*i_private; /* fs or device private pointer */
};

内核用inode结构在内部表示文件, 因此它和file结构不同、 后者表示打开的文件描述符。 对单个文件, 可能会有许多个表示打开的文件描述符的file结构, 但它们都指向单个inode结构。
inode结构中包含了大量有关文件的信息。作为常规,只有下面两个字段对编写驱动程 序代码有用:

  • dev_t i_rdev
    对表示设备文件的inode结构, 该字段包含了真正的设备编号。
  • struct cdev *i_cdev
    struct cdev是表示字符设备的内核的内部结构。当inode指向一个字符设备文件时 , 该字段包含了指向struct cdev结构的指针。
    内核增加了两个新的宏, 可用来从一个 inode中获得主设备号和次设备号:
unsigned int iminor(struct inode *inode); 
unsigned int imajor(struct inode *inode);

字符设备注册和删除

字符设备的注册

内核内部使用struct cdev结构来表示字符设备,代码应包含 <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 *my_cdev = cdev_alloc();
my_cdev->ops =&my_fops; 

需要用下面的代码来初始化已分配到的结构

void cdev_init(struct cdev *cdev, struct file_operations *fops); 

owner字段, 应被设置为THIS_MODULE。

在cdev结构设置好之后 , 最后的步骤是通过下面的调用告诉内核该结构的信息:

nt cdev_add(struct cdev *dev, dev_t num, unsigned int count); 
字段说明
devcdev结构
num该设备对应的第一个设备编号
count和该设 备关联的设备编号的数量

这个调用可能会失败。如果它返回一 个负的错误码, 则设备不会被添加到系统中

移除一个字符设备

void cdev_del(struct cdev *dev); 

cdev_del 和 unregister_chrdev_region 这两个函数合起来的功能相当于 unregister_chrdev 函
数。

一个方便使用的宏

<linux/kernel.h>中的container_of宏

container_of(pointer, container_type, container_field); 

它可用于从包含在某个结构中的指针获得结构本身的指针。

内核代码和用户空间之间移动数据的函数

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

自动创建设备节点

在前面说的添加完字符设备之后,如何让用户看到并能够使用这个设备,根据linux一切皆文件的原则,需要创建一个设备节点文件,一般有两种方法:

手动创建设备节点

[root@localhost ~]# mknod /dev/myled c 2 1      //创建一个设备

以上命令表示创建一个名为"myled"的设备,c 表示字符设备 后续2和1则表示主设备号和次设备号

自动创建设备节点

创建类

自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添
加自动创建设备节点相关代码。首先要创建一个 class 类, class 是个结构体,定义在文件
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)
{
	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);
}

class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。
返回值是个指向结构体 class 的指针,也就是创建的类。

关于结构体class,也定义在device.h 中

/**
 * struct class - device classes
 * @name:	Name of the class.
 * @owner:	The module owner.
 * @class_attrs: Default attributes of this class.
 * @dev_groups:	Default attributes of the devices that belong to the class.
 * @dev_kobj:	The kobject that represents this class and links it into the hierarchy.
 * @dev_uevent:	Called when a device is added, removed from this class, or a
 *		few other things that generate uevents to add the environment
 *		variables.
 * @devnode:	Callback to provide the devtmpfs.
 * @class_release: Called to release this class.
 * @dev_release: Called to release the device.
 * @suspend:	Used to put the device to sleep mode, usually to a low power
 *		state.
 * @resume:	Used to bring the device from the sleep mode.
 * @ns_type:	Callbacks so sysfs can detemine namespaces.
 * @namespace:	Namespace of the device belongs to this class.
 * @pm:		The default device power management operations of this class.
 * @p:		The private data of the driver core, no one other than the
 *		driver core can touch this.
 *
 * A class is a higher-level view of a device that abstracts out low-level
 * implementation details. Drivers may see a SCSI disk or an ATA disk, but,
 * at the class level, they are all simply disks. Classes allow user space
 * to work with devices based on what they do, rather than how they are
 * connected or how they work.
 */
struct class {
	const char		*name;
	struct module		*owner;

	struct class_attribute		*class_attrs;
	const struct attribute_group	**dev_groups;
	struct kobject			*dev_kobj;

	int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
	char *(*devnode)(struct device *dev, umode_t *mode);

	void (*class_release)(struct class *class);
	void (*dev_release)(struct device *dev);

	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);

	const struct kobj_ns_type_operations *ns_type;
	const void *(*namespace)(struct device *dev);

	const struct dev_pm_ops *pm;

	struct subsys_private *p;
};

删除类

卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下(class.c)

/**
 * 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)
{
	if ((cls == NULL) || (IS_ERR(cls)))
		return;

	class_unregister(cls);
}

创建设备

使用 device_create 函数在类下面创建设备, device_create 函数原型如下:
drivers/base/core.c

/**
 * 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, ...)
{
	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 是个可变参数函数,
参数 class 就是设备要创建哪个类下面;
参数 parent 是父设备,一般为 NULL,也就是没有父设备;
参数 devt 是设备号;
参数 drvdata 是设备可能会使用的一些数据,一般为 NULL;
参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件。
返回值就是创建好的设备。


删除设备

卸载驱动的时候需要删除掉创建的设备,设备删除函数为 device_destroy,函数原
型如下:

/**
 * 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)
{
	struct device *dev;

	dev = class_find_device(class, NULL, &devt, __match_devt);
	if (dev) {
		put_device(dev);
		device_unregister(dev);
	}
}

参数 class 是要删除的设备所处的类
参数 devt 是要删除的设备号

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值