字符设备驱动(3)

-- TOC --

接上一篇,这次主要学习read系统调用,使用file结构体的private_data传递驱动内部数据。

先上代码:

$ 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   8

static unsigned int major = 0;
static unsigned int minor_pos = 0; /* count of successful cdev_add */

struct file_operations mychar_fop;
struct mychar_dev {
    struct cdev mycdev;
    char content[CONTENT_LEN];
} *pmychar = NULL;

/* get index for pmychar according to minor number  */
#define INDEX(minor) (minor-MINOR_FIRST)


int mychar_open(struct inode *inode, struct file *fp) {
    unsigned int index;

    printk(KERN_INFO"[mychar] open called, major %u minor %u.\n",
                imajor(inode), iminor(inode));

    /* check flags */
    if ((fp->f_flags & O_ACCMODE) != O_RDONLY)
        return -EPERM;  // operation not permitted 

    /* set fp->private-data to struct mychar_dev */
    index = INDEX(iminor(inode));
    fp->private_data = (void *)(&pmychar[index]);

    return 0;
}


int mychar_release(struct inode *inode, struct file *fp) {
    printk(KERN_INFO"[mychar] release called, major %u minor %u.\n",
                imajor(inode), iminor(inode));
    return 0;
}


ssize_t mychar_read(struct file *fp, char __user *buf,
                    size_t count, loff_t *f_pos) {
    /* get mychar_dev from fp->private_data */
    struct mychar_dev *dev=(struct mychar_dev *)fp->private_data;

    /* test f_pos */
    if (&fp->f_pos == f_pos)
        printk(KERN_INFO"[mychar] &fp->f_pos == f_pos.\n");
    else {
        printk(KERN_INFO"[mychar] fp->f_pos = %llu.\n", fp->f_pos);
        printk(KERN_INFO"[mychar] f_pos = %llu.\n", *f_pos);
        printk(KERN_INFO"[mychar] &fp->f_pos = %px.\n", &fp->f_pos);
        printk(KERN_INFO"[mychar] f_pos = %px.\n", f_pos);
    }

    printk(KERN_INFO"[mychar] read called, count %zu.\n", count);
    if (*f_pos >= CONTENT_LEN) {
        printk(KERN_INFO"[mychar] read call end, return count 0.\n");
        return 0;
    }

    /* update count if reach end */
    if ((*f_pos+count) > CONTENT_LEN)
        count = CONTENT_LEN - *f_pos;

    /* must use copy_to_user, can't derefer user-space */
    if (copy_to_user(buf, dev->content+*f_pos, count))
        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;
}


struct file_operations mychar_fop = {
    .owner   = THIS_MODULE,
    .open    = mychar_open,
    .release = mychar_release,
    .read    = mychar_read,
};


static void mychar_exit(void) {
    int i;

    if (pmychar != NULL) {
        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, j;
    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, fill diff content */
    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,j=-1; i<MINOR_NUM; ++i,j=-1)
        while (++j < CONTENT_LEN)
            pmychar[i].content[j] = i + 48 + j;  // '0' is 48

    /* 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);

这次还是4个minor number,但是从3开始,驱动为每个minor对应一个mychar_dev结构体,此结构体内包含struct cdev

初始化的时候,调用kmalloc申请内容,并且对每个mychar_dev中的content赋值,为了测试,每个device对应的这8个byte都不相同。任何程序读取这些device,获取到的内容都一样!

在初始化和注册cdev的时候,修正了一个bug,因为有可能存在cdev_add失败的情况,此时要释放所有申请的资源,但对cdev_del的调用,只能运行add成功的minor上。代码用minor_pos这个变量来记录哪些minor add成功,在exit中,有针对性的释放。

open系统调用时,首先判断了fp->f_flags,这一版mychar驱动,只能读!然后通过minor number得到驱动内部的mychar_dev结构体地址,将其保存在fp->private_data中。

在read系统调用的时候,将此private_data取出来,它对应的就是驱动内部mychar_dev地址,通过此地址,可以得到device对应的不同的content!

read函数的返回,当大于等于0时,表示成功copy到user-space空间的byte数,小于0时,表示有错误。只能使用kernel提供的copy_to_user接口,将数据传递给user-space!(所谓dereference,就是*p这样的操作,&i取地址叫做reference操作)

copy_to_user,copy_from_user这两个接口定义在linux/uaccess.h中,会检查user-space的地址!返回0表示全部copy成功,部分成功返回还没有copy的字节数。user space的地址空间可以做swap,情况比较复杂,可能存在地址无效的情况(没有物理内存映射),因此必须用kernel给的这两个接口。

更多接口,以及Linux内存管理方面的知识:https://developer.ibm.com/articles/l-kernel-memory-access/

比较奇怪的地方是,read函数接口特别申明了f_pos指针,但fp结构体内部就有一个f_pos成员,我发现这二者貌似值是一样的,但是地址不一样!难道是kernel将f_pos的值又赋给了fp->f_pos,为什么这样设计?

user-space调用read,传递到kernel,count总是8192。

开始测试:

$ pwd
/home/xinlin/test/mychar3
$ ls
load.sh  Makefile  mychar.c  unload.sh
$ make
make -C ~/sources/linux-5.14.14 M=/home/xinlin/test/mychar3 modules
make[1]: Entering directory '/home/xinlin/sources/linux-5.14.14'
  CC [M]  /home/xinlin/test/mychar3/mychar.o
  MODPOST /home/xinlin/test/mychar3/Module.symvers
  CC [M]  /home/xinlin/test/mychar3/mychar.mod.o
  LD [M]  /home/xinlin/test/mychar3/mychar.ko
make[1]: Leaving directory '/home/xinlin/sources/linux-5.14.14'
$ sudo bash load.sh
$ make clean
rm -f *.o *.ko *.cmd *.dwo *.mod *.mod.c Module.symvers modules.order
rm -f .m* .M*
$ ls
load.sh  Makefile  mychar-3  mychar-4  mychar-5  mychar-6  mychar.c  unload.sh
$ dmesg | tail -n2
[1045100.539006] [mychar] major is 237, 4 minor start from 3.
[1045100.539010] [mychar] 4 devices added successfully.

开始读取测试,

$ python3 -q
>>> with open('mychar-3') as f3, open('mychar-4') as f4, \
...      open('mychar-5') as f5, open('mychar-6') as f6:
...   print(f3.read())
...   print(f4.read())
...   print(f5.read())
...   print(f6.read())
... 
01234567
12345678
23456789
3456789:
>>> exit()
$ dmesg
....
[1045667.355146] [mychar] open called, major 237 minor 3.
[1045667.355531] [mychar] open called, major 237 minor 4.
[1045667.355552] [mychar] open called, major 237 minor 5.
[1045667.355605] [mychar] open called, major 237 minor 6.
[1045667.355622] [mychar] fp->f_pos = 0.
[1045667.355622] [mychar] f_pos = 0.
[1045667.355623] [mychar] &fp->f_pos = ffff91884864cf68.
[1045667.355624] [mychar] f_pos = ffffa4864624bef0.
[1045667.355624] [mychar] read called, count 8192.
[1045667.355625] [mychar] read call end, return count 8.
[1045667.355626] [mychar] fp->f_pos = 8.
[1045667.355627] [mychar] f_pos = 8.
[1045667.355627] [mychar] &fp->f_pos = ffff91884864cf68.
[1045667.355627] [mychar] f_pos = ffffa4864624bef0.
[1045667.355628] [mychar] read called, count 8184.
[1045667.355628] [mychar] read call end, return count 0.
[1045667.355652] [mychar] fp->f_pos = 0.
[1045667.355653] [mychar] f_pos = 0.
[1045667.355653] [mychar] &fp->f_pos = ffff91884864c268.
[1045667.355653] [mychar] f_pos = ffffa4864624bef0.
[1045667.355654] [mychar] read called, count 8192.
[1045667.355654] [mychar] read call end, return count 8.
[1045667.355655] [mychar] fp->f_pos = 8.
[1045667.355656] [mychar] f_pos = 8.
[1045667.355656] [mychar] &fp->f_pos = ffff91884864c268.
[1045667.355656] [mychar] f_pos = ffffa4864624bef0.
[1045667.355657] [mychar] read called, count 8184.
[1045667.355657] [mychar] read call end, return count 0.
[1045667.355663] [mychar] fp->f_pos = 0.
[1045667.355664] [mychar] f_pos = 0.
[1045667.355664] [mychar] &fp->f_pos = ffff91884864c968.
[1045667.355665] [mychar] f_pos = ffffa4864624bef0.
[1045667.355665] [mychar] read called, count 8192.
[1045667.355665] [mychar] read call end, return count 8.
[1045667.355666] [mychar] fp->f_pos = 8.
[1045667.355667] [mychar] f_pos = 8.
[1045667.355667] [mychar] &fp->f_pos = ffff91884864c968.
[1045667.355667] [mychar] f_pos = ffffa4864624bef0.
[1045667.355689] [mychar] read called, count 8184.
[1045667.355689] [mychar] read call end, return count 0.
[1045667.355702] [mychar] fp->f_pos = 0.
[1045667.355703] [mychar] f_pos = 0.
[1045667.355703] [mychar] &fp->f_pos = ffff91884864c368.
[1045667.355704] [mychar] f_pos = ffffa4864624bef0.
[1045667.355704] [mychar] read called, count 8192.
[1045667.355705] [mychar] read call end, return count 8.
[1045667.355706] [mychar] fp->f_pos = 8.
[1045667.355706] [mychar] f_pos = 8.
[1045667.355706] [mychar] &fp->f_pos = ffff91884864c368.
[1045667.355707] [mychar] f_pos = ffffa4864624bef0.
[1045667.355707] [mychar] read called, count 8184.
[1045667.355707] [mychar] read call end, return count 0.
[1045667.355715] [mychar] release called, major 237 minor 6.
[1045667.355718] [mychar] release called, major 237 minor 5.
[1045667.355720] [mychar] release called, major 237 minor 4.
[1045667.355722] [mychar] release called, major 237 minor 3.

读出正常,观察到的细节:

  1. python的read函数,不带参数,会一口气将全部内容读出来,每一个python的read,对应两次对驱动read的调用,每次count都是8192,直到驱动read返回0后,不再调用;
  2. 在读取这4个device file时,驱动read得到的f_pos地址是一样的,但fp->f_pos地址不同。所以,驱动的read接口,如果直接给fp-f_pos赋值,可能会导致错误,因为kernel很可能会再把f_pos赋值fp->f_pos!(未测试)

Whatever the amount of data the methods transfer, they should generally update the file position at offp to represent the current file position after successful completion of the system call. The kernel then propagates the file position change back into the file structure when appropriate.*

权限测试:

$ python3 -q
>>> f = open('mychar-5', 'w')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
PermissionError: [Errno 1] Operation not permitted: 'mychar-5'
>>> exit()
$ ll mychar-5
crw-rw-rw- 1 root root 237, 5 12月  8 10:42 mychar-5
$ dmesg | tail -n1
[1046559.873982] [mychar] open called, major 237 minor 5.

虽然device file有w权限,但是驱动程序没有,在open被调用的时候,返回的-EPERM生效了。

测试按字节读取的情况:

$ python3 -q
>>> f = open('mychar-5')
>>> f.read(1)
'2'
>>> f.read(1)
'3'
>>> f.read(1)
'4'
>>> f.read(1)
'5'
>>> f.read(1)
'6'
>>> f.read(1)
'7'
>>> f.read(1)
'8'
>>> f.read(1)
'9'
>>> f.read(1)
''
>>> f.read(1)
''
>>> f.close()
>>> exit()
$ dmesg | tail -n 20
[1048661.527111] [mychar] open called, major 237 minor 5.
[1048666.904165] [mychar] fp->f_pos = 0.
[1048666.904167] [mychar] f_pos = 0.
[1048666.904168] [mychar] &fp->f_pos = ffff91885183d168.
[1048666.904169] [mychar] f_pos = ffffa486475a7ef0.
[1048666.904169] [mychar] read called, count 8192.
[1048666.904170] [mychar] read call end, return count 8.
[1048671.054662] [mychar] fp->f_pos = 8.
[1048671.054665] [mychar] f_pos = 8.
[1048671.054665] [mychar] &fp->f_pos = ffff91885183d168.
[1048671.054666] [mychar] f_pos = ffffa486475a7ef0.
[1048671.054667] [mychar] read called, count 8192.
[1048671.054667] [mychar] read call end, return count 0.
[1048672.031070] [mychar] fp->f_pos = 8.
[1048672.031073] [mychar] f_pos = 8.
[1048672.031073] [mychar] &fp->f_pos = ffff91885183d168.
[1048672.031074] [mychar] f_pos = ffffa486475a7ef0.
[1048672.031075] [mychar] read called, count 8192.
[1048672.031076] [mychar] read call end, return count 0.
[1048677.375072] [mychar] release called, major 237 minor 5.

python前面8个字节的读取,对应1次驱动read的调用,此时已经读取完毕,后面再读了2次,对应2次驱动调用,但这两次返回的都是0。如果python代码每次read 2 bytes,也是一样的效果。

用cat命令也可以测试读取:

$ cat mychar-3
01234567$ cat mychar-4
12345678$ cat mychar-5
23456789$ cat mychar-6
3456789:$ 

使用cat读取时,传入驱动read接口的count,就跟前面不一样了,这应该与具体的实现有关系:

$ cat mychar-6
3456789:$
$ dmesg | tail -n14
[1128259.622854] [mychar] open called, major 237 minor 6.
[1128259.622869] [mychar] fp->f_pos = 0.
[1128259.622870] [mychar] f_pos = 0.
[1128259.622870] [mychar] &fp->f_pos = ffff91884c8a9468.
[1128259.622951] [mychar] f_pos = ffffa48642bd3ef0.
[1128259.622951] [mychar] read called, count 131072.
[1128259.622956] [mychar] read call end, return count 8.
[1128259.622963] [mychar] fp->f_pos = 8.
[1128259.622964] [mychar] f_pos = 8.
[1128259.622964] [mychar] &fp->f_pos = ffff91884c8a9468.
[1128259.622965] [mychar] f_pos = ffffa48642bd3ef0.
[1128259.622965] [mychar] read called, count 131072.
[1128259.622966] [mychar] read call end, return count 0.
[1128259.622990] [mychar] release called, major 237 minor 6.

下一篇计划:

  1. 用另一种更酷的方式获取mychar_dev结构体,不用minor number;
  2. 使用write接口;
  3. 完善对copy_to_user函数的使用。

本文链接:https://cs.pynote.net/sf/linux/dd/202112081/

-- EOF --

-- MORE --