Course design of operating system: add linux driver

Posted by goaman on Tue, 15 Feb 2022 15:51:38 +0100

Write in front

Please read it first https://blog.csdn.net/qq_46640863/article/details/122684580
Compile the linux kernel.

1, Design content and specific requirements

New Linux driver
Add a driver (using memory to simulate the device) and use module compilation.
requirement:
(1) New drivers can be loaded and unloaded dynamically.
(2) Use the driver through the program or command line.
(3) At least 256MB of data can be saved through the driver, and these data can be read out.
(4) To recompile the Linux kernel, you can imitate the implementation of ramdisk.

2, Design idea

The topic requires adding a driver to the memory simulation device. The memory simulation device can imitate the implementation of Ram Disk. After consulting relevant materials, we can know that the function of Ram Disk is to mount a part of memory into a partition of external memory space (disk). From the user's perspective, Ram Disk partition can read and write files just like disk partition.
However, Ram Disk is still different from real disk. After the virtual machine is restarted, the Ram Disk partition disappears, and the data inside the Ram Disk partition will also disappear.
Ram Disk also has its own meaning. If several files need to be read and written frequently, they can be placed on Ram Disk opened by memory, which greatly improves the speed of reading and writing.
In this topic, we adopt to imitate the implementation of Ram Disk. In Chapter 6, we will show the effect similar to Ram Disk that can be obtained by imitating the implementation of Ram Disk.
The Linux system treats all devices as files, / dev / device name is not a directory, but is similar to the pointer pointing to the block of devices, which cannot be read or written directly, but needs to be mounted first. To read and write files in the device, you need to mount the partition of the device to a directory in the system, and access the device by accessing the directory.

3, Design and implementation and source code analysis

When designing your own driver, you need to implement the initialization function when loading the module, that is, the entry function of the driver module. You also need to implement the function when unloading the module, that is, the exit function of the module. At the same time, the device's own request processing function should also be implemented.
Firstly, the data structure of the module is designed. First define the block device name, main device number, size (25610241024 bytes, i.e. 256MB) and the number of sectors of the block as 9.

#define SIMP_BLKDEV_DISKNAME "zombotany_blkdev"
#define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR 
#define SIMP_BLKDEV_BYTES (256*1024*1024)
#define SECTOR_SIZE_SHIFT 9

Define gendisk to represent a simple disk device, define the owner of the block device, define the request queue pointer of the block device, and open up the storage space of the block device.

static struct gendisk * zombotany_blkdev_disk;
static struct block_device_operations  zombotany_blkdev_fops = { 
    .owner = THIS_MODULE,
};
static struct request_queue * zombotany_blkdev_queue;
unsigned char  zombotany_blkdev_data[SIMP_BLKDEV_BYTES];

The method headers of entry function and exit function are as follows:

static int __init _init(void)  
static void __exit _exit(void)  

The functions to be implemented in the entry function include four steps. 1. Apply for equipment resources. If the application fails, exit. 2. Set equipment related attributes. 3. Initialize the request queue and exit if it fails. 4. Add disk block device.
First, apply for equipment resources. Judge whether the application is successful. If it fails, exit.

     zombotany_blkdev_disk = alloc_disk(1);
    if(! zombotany_blkdev_disk){
        ret = -ENOMEM;
        goto err_alloc_disk;
}

Next, set the device related properties. Set the device name, device number, fops pointer and number of sectors

strcpy( zombotany_blkdev_disk->disk_name,SIMP_BLKDEV_DISKNAME);
     zombotany_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
     zombotany_blkdev_disk->first_minor = 0;
     zombotany_blkdev_disk->fops = & zombotany_blkdev_fops;
    set_capacity( zombotany_blkdev_disk, SIMP_BLKDEV_BYTES>>9);

Initialize the request queue and exit if it fails.

     zombotany_blkdev_queue = blk_init_queue( zombotany_blkdev_do_request, NULL);
    if(! zombotany_blkdev_queue){
        ret = -ENOMEM;
        goto err_init_queue;
    }
zombotany_blkdev_disk->queue =  zombotany_blkdev_queue;

Finally, add a disk block device.

    add_disk( zombotany_blkdev_disk);
    return 0;

The exit function of the module is relatively simple. You only need to release the disk block device, release the applied device resources, and clear the request queue.

static void __exit  zombotany_blkdev_exit(void){
    	del_gendisk( zombotany_blkdev_disk);
    	put_disk( zombotany_blkdev_disk);   
    	blk_cleanup_queue( zombotany_blkdev_queue);
}

After implementing the entry and exit functions, you need to declare the module entry and exit.

module_init(xxxx_init);
module_exit(xxxx_exit);

Implement the request processing function of the module. The data structures involved in the request processing function are as follows: current request, current request bio (the general block layer uses bio to manage a request), segment linked list of current request bio, current disk area and buffer.

struct request *req;
struct bio *req_bio;
struct bio_vec *bvec;
char *disk_mem;     
char *buffer;

For a request, first judge whether the request is legal. The way to judge whether the request is legal is to judge whether the address is out of bounds.

if((blk_rq_pos(req)<<SECTOR_SIZE_SHIFT)+blk_rq_bytes(req)>SIMP_BLKDEV_BYTES){
            blk_end_request_all(req, -EIO);
            continue;
        }

If the request is legal, obtain the current address location.

disk_mem =zombotany_blkdev_data + (blk_rq_pos(req) << SECTOR_SIZE_SHIFT);
req_bio = req->bio;

The process of judging the request type and processing read requests and write requests is similar. When processing the read request, traverse the request list, find the buffer and bio, and copy the contents of the disk to the buffer. Find the next area of the disk and process the next request in the request queue.

while(req_bio != NULL){
                for(i=0; i<req_bio->bi_vcnt; i++){
                    bvec = &(req_bio->bi_io_vec[i]);
                    buffer = kmap(bvec->bv_page) + bvec->bv_offset;
                    memcpy(buffer, disk_mem, bvec->bv_len);
                    kunmap(bvec->bv_page);
                    disk_mem += bvec->bv_len;
                }
                req_bio = req_bio->bi_next;
            }
            __blk_end_request_all(req, 0);
            break;

When processing write requests, the contents of the buffer are copied to the disk. Just exchange the two parameters when calling memcpy, and the rest are the same.

memcpy(disk_mem, buffer, bvec->bv_len);

The code of this part is as follows:

while(req_bio != NULL){
                for(i=0; i<req_bio->bi_vcnt; i++){
                    bvec = &(req_bio->bi_io_vec[i]);
                    buffer = kmap(bvec->bv_page) + bvec->bv_offset;
                    memcpy(disk_mem, buffer, bvec->bv_len);
                    kunmap(bvec->bv_page);
                    disk_mem += bvec->bv_len;
                }
                req_bio = req_bio->bi_next;
            }
            __blk_end_request_all(req, 0);
            break;

The complete code of the module is shown in the appendix, and the file name is zombotany_blkdev.c

After writing the module code, you also need to write the makefile file. In the Linux file system, the file has no extension. Makefile file has no extension.
First, when reading and executing this Makefile for the first time, KERNELRELEASE is not defined, so make will only execute the content after else.

ifneq ($(KERNELRELEASE),)

Get the path of the kernel source code and the current working path

KDIR ?= /lib/modules/$(shell uname -r)/build 
	PWD := $(shell pwd)

If Makefile has been executed before, you need to clean up the previously compiled modules.

modules:
		$(MAKE) -C $(KDIR) M=$(PWD) modules
	modules_install:
$(MAKE) -C $(KDIR) M=$(PWD) modules_install
clean:
        rm -rf *.o *.ko .depend *.mod.o *.mod.c Module.* modules.*
.PHONY:modules modules_install clean

Generate o documents

else
		obj-m := simp_blkdev.o
	endif

See the appendix for the complete code of Makefile.

4, Operation process

Zombotany module source code_ blkdev. C and Makefile files are placed in the same directory.
Open the console in this directory and enter make. Can be generated correctly o documentation and ko file

5, Testing and analysis

After the module is compiled, first return to the directory and execute the statement insmod zombotany_blkdev.ko, the newly compiled zombotany_blkdev.ko module insertion. After execution, execute lsmod to view the list of block devices in the current system. As you can see, zombotany_blkdev already exists, has been inserted, and the size is 256MB. You can also execute lsblk to view the current block device.


In the / dev / path under the root directory, you can see zombotany_blkdev has been inserted. Execute ls /dev/

After the insertion is completed, the module needs to be formatted to establish a file system. Enter mkfs ext3 /dev/zombotany_ Blkdev, an ext3 file system is established on the memory emulation device.
After establishing the file system, you can mount the device to the directory of the file system. First, you need to create a directory to mount. mkdir -p /mnt/temp1. Mount the block device to this directory. mount /dev/zombotany_blkdev /mnt/temp1. Run mount | grep zombotany again_ blkdev. The mount is complete.
Execute lsmod again to check whether the module is called. This module is called by a user.

Execute ls/mnt/temp1 / to see that the current block device has only one file: lost+found file. Copy all code blocks in the current directory to the current directory, for example. Execute CP. / */ MNT / temp1 / finish copying, and then check the list of current block device files. Execute ls/mnt/temp1 /, you can see that the block device is correctly written to the file and can be read.

Execute df -H to view the current usage of each device. New device zombotany_blkdev has used 2.9MB.

Execute vi /mnt/temp1/zombotany_blkdev.c. You can also read this file.

Finally, uninstall the module. First delete all files in the directory. rm -rf /mnt/temp1/*.
Cancel the mount first. After executing umount /mnt/temp1, execute lsmod | grep zombotany_blkdev. You can see that this 256MB device is called by 0 users.
Execute rmmod zombotany_blkdev. This statement is used to remove the module. After running, execute lsmod grep zombotany again_ blkdev. You can see on the console that the system has no output. Description: zombotany_ The blkdev module has been completely removed.

appendix

Makefile

ifeq ($(KERNELRELEASE),)
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
modules_install:
	$(MAKE) -C $(KDIR) M=$(PWD) modules_install
clean:
	rm -rf *.o *.ko .depend *.mod.o *.mod.c Module.* modules.*
.PHONY:modules modules_install clean
else
	obj-m := zombotany_blkdev.o
endif

zombotany_blkdev.c

#include <linux/module.h>
#include <linux/blkdev.h>

#define SIMP_BLKDEV_DISKNAME "zombotany_blkdev" / / device name
#define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR / / main equipment No
#define SIMP_BLKDEV_BYTES (256*1024*1024) / / the block device size is 256MB
#define SECTOR_SIZE_SHIFT 9//9 sectors

static struct gendisk * zombotany_blkdev_disk;// The gendisk structure represents a simple disk device
static struct block_device_operations  zombotany_blkdev_fops = { 
    .owner = THIS_MODULE,//Equipment body
};
static struct request_queue * zombotany_blkdev_queue;//Pointer to the block device request queue
unsigned char  zombotany_blkdev_data[SIMP_BLKDEV_BYTES];// Storage space of virtual disk block device

//Request processing function
static void  zombotany_blkdev_do_request(struct request_queue *q){
    struct request *req;// Requests in the processing request queue
    struct bio *req_bio;// Current requested bio
    struct bio_vec *bvec;// Linked list of bio segments currently requested
    char *disk_mem;      // Disk area to read / write
    char *buffer;        // A buffer in memory for requests from disk block devices

    while((req = blk_fetch_request(q)) != NULL){//Get request
        // Judge whether the current request is legal
        if((blk_rq_pos(req)<<SECTOR_SIZE_SHIFT) + blk_rq_bytes(req) > SIMP_BLKDEV_BYTES){//Determine whether the address has cross-border access
            printk(KERN_ERR SIMP_BLKDEV_DISKNAME":bad request:block=%llu, count=%u\n",(unsigned long long)blk_rq_pos(req),blk_rq_sectors(req));//If the access is out of bounds, the output
            blk_end_request_all(req, -EIO);
            continue;//Get next request
        }
        //Get the memory location where the operation is required
        disk_mem =  zombotany_blkdev_data + (blk_rq_pos(req) << SECTOR_SIZE_SHIFT);
        req_bio = req->bio;// Get the bio of the current request

        switch (rq_data_dir(req)) {  //Determine the type of request
        case READ:
            // Traverse the bio linked list of req requests
            while(req_bio != NULL){
                //The for loop handles bio in the bio structure_ VEC structure array (bio_vec structure array represents a complete buffer)
                for(int i=0; i<req_bio->bi_vcnt; i++){
                    bvec = &(req_bio->bi_io_vec[i]);
                    buffer = kmap(bvec->bv_page) + bvec->bv_offset;
                    memcpy(buffer, disk_mem, bvec->bv_len);//Copy data in memory to buffer
                    kunmap(bvec->bv_page);
                    disk_mem += bvec->bv_len;
                }
                req_bio = req_bio->bi_next;//Request the next item in the linked list
            }
            __blk_end_request_all(req, 0);//It has been traversed
            break;
        case WRITE:
            while(req_bio != NULL){
                for(int i=0; i<req_bio->bi_vcnt; i++){
                    bvec = &(req_bio->bi_io_vec[i]);
                    buffer = kmap(bvec->bv_page) + bvec->bv_offset;
                    memcpy(disk_mem, buffer, bvec->bv_len);//Copy the data in the buffer to memory
                    kunmap(bvec->bv_page);
                    disk_mem += bvec->bv_len;
                }
                req_bio = req_bio->bi_next;//Request the next item in the linked list
            }
            __blk_end_request_all(req, 0);//End of request list traversal
            break;
        default:
            /* No default because rq_data_dir(req) is 1 bit */
            break;
        }
    }
}


//Module entry function
static int __init  zombotany_blkdev_init(void){
    int ret;

    //Before adding a device, first apply for the resource of the device
     zombotany_blkdev_disk = alloc_disk(1);
    if(! zombotany_blkdev_disk){
        ret = -ENOMEM;
        goto err_alloc_disk;
    }

    //Set the relevant attributes of the device (device name, device number, fops pointer)
    strcpy( zombotany_blkdev_disk->disk_name,SIMP_BLKDEV_DISKNAME);
     zombotany_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
     zombotany_blkdev_disk->first_minor = 0;
     zombotany_blkdev_disk->fops = & zombotany_blkdev_fops;
    //Pass the address of the block device request processing function into BLK_ init_ The queue function initializes a request queue
     zombotany_blkdev_queue = blk_init_queue( zombotany_blkdev_do_request, NULL);
    if(! zombotany_blkdev_queue){
        ret = -ENOMEM;
        goto err_init_queue;
    }
     zombotany_blkdev_disk->queue =  zombotany_blkdev_queue;
	//Number of initialization sectors
    set_capacity( zombotany_blkdev_disk, SIMP_BLKDEV_BYTES>>9);

    //Add disk block device at the entrance
    add_disk( zombotany_blkdev_disk);
    return 0;

    err_alloc_disk:
        return ret;
    err_init_queue:
        return ret;
}


//Exit function of module
static void __exit  zombotany_blkdev_exit(void){
// Release disk block device
    del_gendisk( zombotany_blkdev_disk);
// Release requested device resources
    put_disk( zombotany_blkdev_disk);   
// Clear request queue
    blk_cleanup_queue( zombotany_blkdev_queue);
}


module_init( zombotany_blkdev_init);// Declare the entry of the module
module_exit( zombotany_blkdev_exit);// Declare the exit of the module

Topics: Linux Operation & Maintenance server