Brief Summary
The block abstraction is a unified translation layer designed by your OS to access different storage devices at the granularity of blocks (e.g., 512 bytes). This abstracts away differences between operations supported by storage devices. For instance, hard disk drives (HDDs) write equal-sized sectors, while solid state drives (SSDs) write at the granularity of a single page or collection of pages, depending on the state of the flash. Please refer to lecture #17 for more details.
Under typical circumstances, the block abstraction is invisible to user programs, which read or write to files. However, it is useful for user programs to have fine-grained control over blocks in some cases. For instance, the dd command in Linux OSs uses the block layer to perform tasks like formatting disks and burning new images. Similarly, the mkfs command initializes a new file system on a storage device using the block abstraction. Both commands are user programs you can execute from the terminal.
In this project, your task is to write a kernel module that allows a user program (i.e., our testcases) to directly access a virtual USB storage device using the block abstraction. Your kernel module should support both read and write operations of different block sizes, and at different offsets.
- sudo apt update
- sudo apt install git
- git clone -b TBA https://github.com/ASTERISC-Teaching/cse330-public.git
Directory breakdown. The repository contains the following directories: • outputs: Output for all testcases.
- testcases: All the testcases for this project.
- kmodule: The skeleton code for the kernel module.
- sh: Script that allows you to run a test.
| Important note | |
| • This document only contains high-level instructions for completing this project; they are NOT EXACT step-by-step instructions. You MUST figure out the steps on your own.
• This project requires you to use the kernel (Linux v6.6.9) you compiled in project #1. If you |
|
are changing your group, it is okay to get the VM disk from your previous group and use it.
- Remember to add debug statements inside the kernel module (e.g., using printk) to see what your code is doing and debug the operations. This applies to all steps of the project.
- We provide you all testcases but it is your job to understand what these testcases are doing.
- If your assignment does not compile or your upload is corrupted, you will get a zero.
Always double-check your submission.
1 Prerequisite Step: Add Virtual USB Storage to VM
The first step is to add a 1GB USB (virtual) storage disk to your VM. I will leave this up to you to figure out from the settings of VirtualBox/UTM/VMWare Fusion, etc. Hint: Google it!
lsblk command provides you a list of block devices registered The (virtual) USB should already be registered and given a name (e.g., /dev/sdb).
Please complete this step before you move on! Also make sure it is 1GB, or the test script will not find it.
2 Background: Linux Block IO (BIO) Implementation
This section provides a set of internal kernel functions and data structures that you can use to access Linux’s block layer from your kernel module.
- struct block_device *blkdev_get_by_path(…): You can use this function to open a device with certain permissions. This is required before any block operation can be performed.
Example: blkdev_get_by_path(“/dev/sdb”, FMODE_READ, NULL, NULL); This opens the “/dev/sda” device for reading operations.
- struct bio: A data structure used for Block I/O communication within Linux. You can allocate a bio using the function bio_alloc(..). The bio data structure contains two important fields for your project.
- bio->bi_iter.bi_sector: Which block (also called sector in Linux) within the storage device to read from or write to.
- bio->bi_opf: Which operation to perform (e.g., REQ_OP_READ is for read).
Example: bio_alloc(bdevice, 1, REQ_OP_READ, GFP_NOIO);
This creates a bio device with a single buffer for reading operations on bdevice.
Note: After using a bio for a read/write operation, you can reset it using the function bio_reset(..). •int bio_add_page(..): Adds a kernel page to the bio. Whichever page you specify, is the page that will be used for the I/O operation.
Example: bio_add_page(bio, page, 512, 0);
Adds a kernel page (physical address) to bio, and specifies that the operation (read/write depending on the value of bio->bi_opf) should be of size 512 bytes. The last “0” signifies that the operation must be performed at page+0 offset. For instance, consider a read operation. The device will write contents of disk to page+0 within the kernel’s memory.
Notes:
- 512 bytes is the largest block operation supported. If you have larger than 512 bytes data to read or write, you must break it into 512 byte chunks.
- You can get the page of a kernel address using vmalloc_to_page(addr)
- int submit_bio_wait(bio): Submits a BIO request and waits for it to be completed. The return value will be the bytes read or written.
Pseudocode:
| // Receive IOCTL call from userspace
// Copy userspace arguments into kernel_buffer (check project 2/3) // Use Linux block abstraction to open the audit device. bdevice = blkdev_get_by_path(…); // Allocate a BIO for use with N buffers (1 in this case) bio = bio_alloc(bdevice, 1, REQ_OP_READ, GFP_NOIO); // Set BIO with device (not needed if you reallocate BIO each time) bio_set_dev(bio, bdevice); // Set BIO sector (0 in this case) and operation bio->bi_iter.bi_sector = 0; bio->bi_opf = REQ_OP_READ; // Add kernel_buffer page to BIO with its correct offset bio_add_page(bio, vmalloc_to_page(kernel_buffer), 512, page_offset); // Submit BIO to the device driver and wait for response submit_bio_wait(bio); // Copy kernel_buffer back to userspace (if needed) // Return from IOCTL call |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
3 Task: Support Block Operations from Userspace using Kernel Module Using Linux’s Block I/O (BIO) implementation (previous section), your kernel module should support the following operations through IOCTL calls. Provided module contains skeleton code for IOCTL calls.
- WRITE (buffer, size): Write buffer (of length size) at the “current” offset within the USB device. Initially current offset is “0”. Each WRITE increases the offset by size.
- READ (buffer, size): Read data (of length size) from the USB device at the “current” offset into buffer. Initially current offset is “0”. Each READ increases the offset by size.
- WRITEOFFSET (buffer, size, offset): Same as WRITE, but instead of current offset, use the provided offset. Change current offset to the provided one.
- READOFFSET (buffer, size, offset): Same as READ, but instead of current offset, use the provided offset. Change current offset to the provided one.
Note: Both size and offset are in bytes.
4 Grading Rubric
There are 100 points available for this assignment. To test your assignment, you must run the test.sh script we have provided. Note: Some of these tests may take several (<5) minutes. One good way to know whether the test is still running is to add prints in the module and check the kernel logs.
| ./test.sh <test> <operationsize> <count> <offset> |
1
- test: read, write, read-variable, or write-variable. The code is provided under “testcases/” operationsize: The size in READ/WRITE/READOFFSET/WRITEOFFSET
- count: Number of operations to perform.
- offset: Offset for READOFFSET/WRITEOFFSET. If 0, executes READ/WRITE.
Test Case Scoring
- ./test.sh read (15 points)
- ./test.sh write (15 points)
- ./test.sh read-variable 512 1 0 (5 points)
- ./test.sh write-variable 512 1 0 (5 points)
- ./test.sh read-variable 512 1024 0 (5 points)
- ./test.sh write-variable 512 100000 0 (5 points) ./test.sh read-variable 2048 100000 0 (5 points) • ./test.sh write-variable 8192 10000 0 (5 points) • ./test.sh read-variable 16384 10000 0 (5 points) • ./test.sh write-variable 131072 768 0 (5 points) • ./test.sh read-variable 512 10000 131072 (5 points) • ./test.sh write-variable 2048 10000 16384 (5 points)
- ./test.sh read-variable 16384 10000 1048576 (5 points)
- ./test.sh read-variable 8192 100 134217728 (5 points) ./test.sh write-variable 8192 512 402653184 (5 points)
- ./test.sh write-variable 393216 1024 402653184 (5 points)
5 Submission Details
Please place the following in one zip file and submit on canvas.
- The kmodule Please do not submit any binaries/.ko files.
- Output screenshots that show your code is working for each test case.
- A README text file clearly describing the name of the members within your group and any other details you want the TAs to know.





