-- TOC --
接上一篇,这次主要实现第1个write系统调用接口,并使用kernel提供的container_of黑函数。
代码如下:
$ cat mychar.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
MODULE_LICENSE("GPL");
#define MINOR_FIRST 3
#define MINOR_NUM 4
#define CONTENT_LEN (1024*1024) // 1MiB bytes space
static unsigned int major = 0;
static unsigned int minor_pos = 0; /* count of successful cdev_add */
struct file_operations mychar_fop;
struct mychar_dev {
char *cont;
size_t len;
struct cdev mycdev;
} *pmychar = NULL;
int mychar_open(struct inode *inode, struct file *fp) {
unsigned int flags;
/* check flags */
flags = fp->f_flags & O_ACCMODE;
if ((flags != O_RDONLY) && (flags != O_WRONLY))
return -EPERM; // operation not permitted
fp->private_data = (void *)container_of(inode->i_cdev,
struct mychar_dev, mycdev);
return 0;
}
int mychar_release(struct inode *inode, struct file *fp) {
return 0;
}
ssize_t mychar_read(struct file *fp, char __user *buf,
size_t count, loff_t *f_pos) {
unsigned long left;
struct mychar_dev *dev=(struct mychar_dev *)fp->private_data;
printk(KERN_INFO"[mychar] read called, count %zu.\n", count);
if (*f_pos >= dev->len) {
printk(KERN_INFO"[mychar] read call end, return count 0.\n");
return 0;
}
/* update count if reach end */
if ((*f_pos+count) > dev->len)
count = dev->len - *f_pos;
/* must use copy_to_user, can't derefer user-space */
if ((left = copy_to_user(buf,dev->cont+*f_pos,count))) {
if (left != count) {
count -= left;
printk(KERN_INFO"[mychar] copy_to_user return less than count!\n");
}
else return -EFAULT;
}
/* update *f_pos */
*f_pos += count;
/* return how many char readed */
printk(KERN_INFO"[mychar] read call end, return count %zu.\n", count);
return count;
}
/* Bug: no support concurrent writing... */
ssize_t mychar_write(struct file *fp, const char __user *buf,
size_t count, loff_t *f_pos) {
unsigned long left;
struct mychar_dev *dev=(struct mychar_dev *)fp->private_data;
printk(KERN_INFO"[mychar] write called, count %zu.\n", count);
/* make each write to the end of the buffer */
*f_pos = dev->len;
printk(KERN_INFO"[mychar] *f_pos = %llu\n", *f_pos);
if (*f_pos >= CONTENT_LEN) {
printk(KERN_INFO"[mychar] write call end, return count 0.\n");
return -ENOSPC;
}
if ((*f_pos+count) > CONTENT_LEN)
count = CONTENT_LEN - *f_pos;
if ((left = copy_from_user(dev->cont+*f_pos,buf,count))) {
if (left != count) {
count -= left;
printk(KERN_INFO"[mychar] copy_from_user return less than count!\n");
}
else return -EFAULT;
}
*f_pos += count;
dev->len += count; // update real length
printk(KERN_INFO"[mychar] write call end, return count %zu.\n", count);
return count;
}
struct file_operations mychar_fop = {
.owner = THIS_MODULE,
.open = mychar_open,
.release = mychar_release,
.read = mychar_read,
.write = mychar_write,
};
static void mychar_exit(void) {
int i;
if (pmychar != NULL) {
for (i=0; i<MINOR_NUM; ++i)
if (pmychar[i].cont != NULL) kfree(pmychar[i].cont);
for (i=0; i<minor_pos; ++i)
cdev_del(&pmychar[i].mycdev);
kfree(pmychar);
}
unregister_chrdev_region(MKDEV(major,MINOR_FIRST), MINOR_NUM);
printk(KERN_INFO"[mychar] exit.\n");
}
static int __init mychar_init(void) {
int rn, i;
dev_t dev;
/* get a major number */
if ((rn = alloc_chrdev_region(&dev,MINOR_FIRST,MINOR_NUM,"mychar"))) {
printk(KERN_WARNING"[mychar] can't get major number, err %d.\n", rn);
return rn;
}
major = MAJOR(dev);
printk(KERN_INFO"[mychar] major is %d, %d minor start from %d.\n",
major, MINOR_NUM, MINOR_FIRST);
/* alloc mychar */
pmychar = kmalloc(MINOR_NUM*sizeof(struct mychar_dev), GFP_KERNEL);
if (pmychar == NULL) {
mychar_exit();
return -ENOMEM;
}
memset(pmychar, 0, MINOR_NUM*sizeof(struct mychar_dev));
for (i=0; i<MINOR_NUM; ++i)
if ((pmychar[i].cont=kmalloc(CONTENT_LEN,GFP_KERNEL)) == NULL) {
mychar_exit();
return -ENOMEM;
}
/* init and add cdev */
for (i=0; i<MINOR_NUM; ++i) {
cdev_init(&pmychar[i].mycdev, &mychar_fop);
if((rn = cdev_add(&pmychar[i].mycdev,MKDEV(major,MINOR_FIRST+i),1))){
printk(KERN_WARNING"[mychar] cdev_add err %d, minor %d.\n",
rn, i+MINOR_FIRST);
mychar_exit();
return rn;
}
++minor_pos;
}
printk(KERN_INFO"[mychar] %d devices added successfully.\n",minor_pos);
/* success return */
return 0;
}
module_init(mychar_init);
module_exit(mychar_exit);
在init模块的时候申请内存,每个device固定1M,在exit的时候释放。
可以通过只读或只写的方式open。用container_of
接口获得mychar_dev结构体的地址。此函数定义在<linux/kernel.h>
中:
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
做一些检查后,直接通过减法计算地址!上一篇的代码用minor number去对应地址,这两种方式都可以,也许后者在速度和可读性上更好。
write接口调用copy_from_user
实现将用户控件的内容copy进device自己的1M控件内,用dev->len
来记录真实的长度。这个版本write接口有一个缺陷,还不能支持并发!先记录在这里...
对copy_to_user
和copy_from_user
接口的调用做了优化,使用其返回值,如果left非零,表示部分成功,此时更新count后返回,也许下次在进来的时候,这两个函数就出错了!在kernel搜索代码,发现很多调用这两个接口的代码,都没有这样使用,一旦left为非零,就直接返回-EFAULT。
测试:
$ sudo bash unload.sh
$ sudo bash load.sh
$ cp /dev/urandom mychar-6
cp: error writing 'mychar-6': No space left on device
xinlin@K:~/test/mychar4$ cat mychar-6 | wc -c
1048576
1048576 bytes 就是 1M!
更多测试就略了,各位同学自己玩...
本文链接:https://cs.pynote.net/sf/linux/dd/202112101/
-- EOF --
-- MORE --