字符设备驱动(2)

-- TOC --

将上一篇的驱动稍微做复杂并完善一些,注册4个minor,使用比较完善的Makefile,制作load和unload的shell脚本。

这次测试需要4个文件:

$ tree
.
├── load.sh
├── Makefile
├── mychar.c
└── unload.sh

0 directories, 4 files

mychar驱动源码:

$ cat mychar.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
MODULE_LICENSE("GPL");


#define MINOR_FIRST 5
#define MINOR_NUM   4 

unsigned int major = 0;
struct cdev mychar[MINOR_NUM];
struct file_operations mychar_fop;


int mychar_open(struct inode *inode, struct file *fp) {
    printk(KERN_INFO"[mychar] open called, major %u minor %u.\n",
                imajor(inode), iminor(inode));
    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;
}


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


static void mychar_exit(void) {
    int i;

    for (i=0; i<MINOR_NUM; ++i) cdev_del(&mychar[i]);
    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);

    /* init and add cdev */
    memset(mychar, 0, sizeof(struct cdev)*MINOR_NUM);
    for (i=0; i<MINOR_NUM; ++i) {
        cdev_init(&mychar[i], &mychar_fop);
        if ((rn = cdev_add(&mychar[i],MKDEV(major,i+MINOR_FIRST),1))) {
            printk(KERN_WARNING"[mychar] cdev_add err %d, minor %d.\n", 
                        rn, i+MINOR_FIRST);
            mychar_exit();
            return rn;
        }
    }

    return 0;
}


module_init(mychar_init);
module_exit(mychar_exit);

为了测试,刻意将minor设置为从5开始,连续4个。

Makefile如下:

$ cat Makefile
obj-m := mychar.o

PWD := $(shell pwd)
SRC := ~/sources/linux-5.14.14

default:
    $(MAKE) -C $(SRC) M=$(PWD) modules

clean:
    rm -f *.o *.ko *.cmd *.dwo *.mod *.mod.c Module.symvers modules.order
    rm -f .m* .M*

make:编译驱动;

make clean:删除所有过程文件。

由于驱动要管理4个minor,在mknod的时候,手动查找major,手动创建node等操作都比较繁琐,因此全部脚本化,下面是load和unload脚本:

$ cat load.sh 
#!/usr/bin/bash

device=mychar

insmod ${device}.ko || exit 1
rm -f ${device}-{5..8}
major=$(awk "\$2==\"${device}\" {print \$1}" /proc/devices)

for i in {5..8}; do
    mknod -m 666 ${device}-$i c ${major} $i 
done

$ cat unload.sh 
#!/usr/bin/bash

device=mychar
rm -f ${device}-{5..8}
rmmod $device

这4个文件都准备好后,开始测试:

$ pwd
/home/xinlin/test/mychar2
$ ls
load.sh  Makefile  mychar.c  unload.sh
$ make
make -C ~/sources/linux-5.14.14 M=/home/xinlin/test/mychar2 modules
make[1]: Entering directory '/home/xinlin/sources/linux-5.14.14'
  CC [M]  /home/xinlin/test/mychar2/mychar.o
  MODPOST /home/xinlin/test/mychar2/Module.symvers
  CC [M]  /home/xinlin/test/mychar2/mychar.mod.o
  LD [M]  /home/xinlin/test/mychar2/mychar.ko
make[1]: Leaving directory '/home/xinlin/sources/linux-5.14.14'
$ sudo bash load.sh 
$ python3 -c 'open("mychar-5").close()'
$ python3 -c 'open("mychar-7").close()'
$ python3 -c 'open("mychar-6").close()'
$ python3 -c 'open("mychar-8").close()'
$ sudo bash unload.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.c  unload.sh
$ dmesg | tail -n10
[894999.337701] [mychar] major is 237, 4 minor start from 5.
[895040.259197] [mychar] open called, major 237 minor 5.
[895040.259496] [mychar] release called, major 237 minor 5.
[895043.090805] [mychar] open called, major 237 minor 7.
[895043.091057] [mychar] release called, major 237 minor 7.
[895046.650857] [mychar] open called, major 237 minor 6.
[895046.651108] [mychar] release called, major 237 minor 6.
[895049.562412] [mychar] open called, major 237 minor 8.
[895049.562658] [mychar] release called, major 237 minor 8.
[895066.520915] [mychar] exit.

Nice~~!!下次开始研究read和write接口。

open系统调用会创建file结构体,当kernel要释放file结构体之前,才会驱动的release!fork系统调用,只是增加file结构体内的count,并不会创建新的file结构体。用户程序关闭时,kernel会自动将为close的file进行关闭,用户程序可以比较lazy。当file的count为0的时候,驱动的release被调用。如果在Python中,连续两次open,相当于两次open系统调用,两次创建的file结构体不同。

BUGS:

如果4个device只有部分add成功,exit的时候确还是全部做del,此时会看到这样的info:

[961914.546065] [mychar] major is 237, 4 minor start from 5.
[961918.226288] ------------[ cut here ]------------
[961918.226321] kobject: '(null)' (00000000cab924f3): is not initialized, yet kobject_put() is being called.
[961918.226329] WARNING: CPU: 0 PID: 481630 at lib/kobject.c:750 kobject_put+0x52/0x1a0
[961918.226333] Modules linked in: mychar(OE-) vsock_loopback vmw_vsock_virtio_transport_common vmw_vsock_vmci_transport vsock nls_iso8859_1 snd_ens1371 intel_rapl_msr snd_ac97_codec intel_rapl_common gameport ac97_bus snd_pcm crct10dif_pclmul ghash_clmulni_intel aesni_intel snd_seq_midi crypto_simd snd_seq_midi_event vmw_balloon snd_rawmidi cryptd snd_seq joydev input_leds rapl snd_seq_device snd_timer snd soundcore serio_raw vmw_vmci mac_hid sch_fq_codel vmwgfx ttm drm_kms_helper cec rc_core fb_sys_fops syscopyarea sysfillrect sysimgblt msr parport_pc ppdev lp drm parport ip_tables x_tables autofs4 hid_generic usbhid hid crc32_pclmul psmouse ahci libahci pcnet32 mii mptspi mptscsih mptbase i2c_piix4 scsi_transport_spi pata_acpi [last unloaded: mychar]
[961918.226364] CPU: 0 PID: 481630 Comm: rmmod Tainted: G        W  OE     5.14.14 #1
[961918.226384] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/22/2020
[961918.226386] RIP: 0010:kobject_put+0x52/0x1a0
[961918.226387] Code: 24 38 83 f8 01 74 28 85 c0 0f 8e 9b 00 00 00 5b 41 5c 41 5d 41 5e 5d c3 48 8b 37 48 89 fa 48 c7 c7 88 d8 7f 91 e8 87 67 5b 00 <0f> 0b eb c3 c3 4d 8b 74 24 18 49 8b 5c 24 28 4d 8b 2c 24 66 90 48
[961918.226389] RSP: 0018:ffffa48642bf3e80 EFLAGS: 00010282
[961918.226390] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000027
[961918.226391] RDX: 0000000000000027 RSI: 00000000ffff7fff RDI: ffff9188bbe189c8
[961918.226392] RBP: ffffa48642bf3ea0 R08: ffff9188bbe189c0 R09: ffffa48642bf3c58
[961918.226393] R10: 0000000000000001 R11: 0000000000000001 R12: ffffffffc05ce5b8
[961918.226394] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000
[961918.226395] FS:  00007fead8222540(0000) GS:ffff9188bbe00000(0000) knlGS:0000000000000000
[961918.226412] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[961918.226412] CR2: 00005648a4909658 CR3: 0000000012156006 CR4: 00000000003706f0
[961918.226431] Call Trace:
[961918.226450]  cdev_del+0x28/0x30
[961918.226455]  mychar_exit+0x39/0x5d [mychar]
[961918.226457]  __x64_sys_delete_module+0x14a/0x260
[961918.226460]  ? exit_to_user_mode_prepare+0x3c/0x1e0
[961918.226462]  do_syscall_64+0x3b/0xc0
[961918.226465]  entry_SYSCALL_64_after_hwframe+0x44/0xae
[961918.226468] RIP: 0033:0x7fead836ebcb
[961918.226469] Code: 73 01 c3 48 8b 0d c5 82 0c 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa b8 b0 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 95 82 0c 00 f7 d8 64 89 01 48
[961918.226470] RSP: 002b:00007fff2f2434c8 EFLAGS: 00000206 ORIG_RAX: 00000000000000b0
[961918.226472] RAX: ffffffffffffffda RBX: 00005648a48fe750 RCX: 00007fead836ebcb
[961918.226473] RDX: 000000000000000a RSI: 0000000000000800 RDI: 00005648a48fe7b8
[961918.226473] RBP: 00007fff2f243528 R08: 0000000000000000 R09: 0000000000000000
[961918.226474] R10: 00007fead83eaac0 R11: 0000000000000206 R12: 00007fff2f243700
[961918.226475] R13: 00007fff2f2457a1 R14: 00005648a48fe2a0 R15: 00005648a48fe750
[961918.226476] ---[ end trace 54efefb107b6bdc6 ]---
[961918.226478] [mychar] exit.

驱动模块还是能够退出!

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

-- EOF --

-- MORE --