The Linux102 series will detail 102 files in version 0.11 of Linux. This article discusses the source code of the 45th file,
<span>【Linux102】45-kernel/blk_drv/ramdisk.c</span>.
1. Main Function of ramdisk.c
This is a memory virtual disk driver, written by Theodore Ts’o. It means that:
If RAMDISK is specified in the linux/Makefile, it indicates that the root filesystem image file will be stored starting from the 256th disk block on the boot disk.
During initialization, the system will allocate a portion of memory for virtual disk operations, the size of which is specified by RAMDISK (in kilobytes). Before loading the normal root filesystem, the system will execute the rd_load() function, attempting to read the root filesystem superblock from block 257 of the disk. If successful, it reads the root filesystem image file into the memory virtual disk and sets the root filesystem device flag ROOT_DEV to the virtual disk device (<span>0x0101</span>), otherwise it exits <span>rd_load()</span> and the system continues to load the root from other devices.
2. Files Used in the Source Code
| File Name | Function |
|---|---|
| 😉【Linux102】20-include/linux/kernel.h | This program mainly defines some of the most commonly used basic function declarations in the kernel, which are fundamental for kernel operation, such as malloc, printk, free, tty_write, panic, etc. The specific implementations of these functions are distributed across various .c files. |
| 😉【Linux102】21-include/asm/segment.h | This program defines a series of inline functions for accessing user space memory in the Linux kernel, mainly through inline assembly to manipulate the segment register fs to achieve data interaction between the kernel and user space. |
| 😉【Linux102】24-include/linux/fs.h | This file (fs.h) is the core header file for the filesystem module in the operating system kernel, which mainly defines the data structures, constants, macros, and function interfaces required for filesystem implementation, providing a foundational framework for the operation of the entire filesystem. |
| 😉【Linux102】36-include/asm/system.h | This file defines embedded assembly macros for setting or modifying descriptors/interruption gates. The function move_to_user_mode() is used for the kernel to “switch” to the initial process (task 0) after initialization is complete. The method used is to simulate the return process of an interrupt call, utilizing the instruction iret to run the initial task 0. |
| 😉【Linux102】42-include/linux/config.h | config.h is a configuration file used to set keyboard layouts and hard disk parameters (as a backup). The design purpose is to provide configurable options to adapt to different hardware environments. |
| 😉【Linux102】40-kernel/blk_drv/blk.h | This is the header file related to disk block device parameters, as it is only used for block devices, it is placed in the same location as the block device code. It mainly defines the data structure request in the request wait queue and defines the elevator scheduling algorithm with macro statements, and sets constant values for the currently supported virtual disks, hard disks, and floppy disks, based on their respective major device numbers. |
| 😉【Linux102】47-include/string.h | Declares a series of standard string manipulation functions and memory block manipulation functions. |
| 😉【Linux102】46-include/asm/memory.h | Main Function: memcpy is used to copy n bytes from a contiguous memory area (source address src) to another contiguous memory area (destination address dest), returning the destination address dest.Usage Scenario: Suitable for scenarios requiring efficient memory data copying, such as data buffer copying, structure copying, memory block migration, etc. However, note: the source and destination addresses must not overlap (for overlapping memory, use memmove). |
3. Source Code Version
/*
* linux/kernel/blk_drv/ramdisk.c
*
* Written by Theodore Ts'o, 12/2/91
*/
#include <string.h>
#include <linux/config.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <asm/memory.h>
#define MAJOR_NR 1
#include "blk.h"
char *rd_start;
int rd_length = 0;
void do_rd_request(void)
{
int len;
char *addr;
INIT_REQUEST;
addr = rd_start + (CURRENT->sector << 9);
len = CURRENT->nr_sectors << 9;
if ((MINOR(CURRENT->dev) != 1) || (addr + len > rd_start + rd_length))
{
end_request(0);
goto repeat;
}
if (CURRENT->cmd == WRITE)
{
(void)memcpy(addr,
CURRENT->buffer,
len);
}
else if (CURRENT->cmd == READ)
{
(void)memcpy(CURRENT->buffer,
addr,
len);
}
else
panic("unknown ramdisk-command");
end_request(1);
goto repeat;
}
/*
* Returns amount of memory which needs to be reserved.
*/
long rd_init(long mem_start, int length)
{
int i;
char *cp;
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
rd_start = (char *)mem_start;
rd_length = length;
cp = rd_start;
for (i = 0; i < length; i++)
*cp++ = '\0';
return (length);
}
/*
* If the root device is the ram disk, try to load it.
* In order to do this, the root device is originally set to the
* floppy, and we later change it to be ram disk.
*/
void rd_load(void)
{
struct buffer_head *bh;
struct super_block s;
int block = 256; /* Start at block 256 */
int i = 1;
int nblocks;
char *cp; /* Move pointer */
if (!rd_length)
return;
printk("Ram disk: %d bytes, starting at 0x%x\n", rd_length,
(int)rd_start);
if (MAJOR(ROOT_DEV) != 2)
return;
bh = breada(ROOT_DEV, block + 1, block, block + 2, -1);
if (!bh)
{
printk("Disk error while looking for ramdisk!\n");
return;
}
*((struct d_super_block *)&s) = *((struct d_super_block *)bh->b_data);
brelse(bh);
if (s.s_magic != SUPER_MAGIC)
/* No ram disk image present, assume normal floppy boot */
return;
nblocks = s.s_nzones << s.s_log_zone_size;
if (nblocks > (rd_length >>> BLOCK_SIZE_BITS))
{
printk("Ram disk image too big! (%d blocks, %d avail)\n",
nblocks, rd_length >>> BLOCK_SIZE_BITS);
return;
}
printk("Loading %d bytes into ram disk... 0000k",
nblocks << BLOCK_SIZE_BITS);
cp = rd_start;
while (nblocks)
{
if (nblocks > 2)
bh = breada(ROOT_DEV, block, block + 1, block + 2, -1);
else
bh = bread(ROOT_DEV, block);
if (!bh)
{
printk("I/O error on block %d, aborting load\n",
block);
return;
}
(void)memcpy(cp, bh->b_data, BLOCK_SIZE);
brelse(bh);
printk("\010\010\010\010\010%4dk", i);
cp += BLOCK_SIZE;
block++;
nblocks--;
i++;
}
printk("\010\010\010\010\010done \n");
ROOT_DEV = 0x0101;
}
4. Annotated Source Code Version
/*
* linux/kernel/blk_drv/ramdisk.c
*
* Written by Theodore Ts'o, 12/2/91
* Memory disk (ramdisk) driver: simulates a portion of memory as a disk device
*/
#include <string.h> // Declarations for string and memory manipulation functions
#include <linux/config.h> // Kernel configuration options
#include <linux/sched.h> // Process scheduling related definitions
#include <linux/fs.h> // Filesystem related definitions
#include <linux/kernel.h> // Core kernel functions and macros
#include <asm/system.h> // Assembly related system functions
#include <asm/segment.h> // Memory segment related definitions
#include <asm/memory.h> // Memory management related definitions
#define MAJOR_NR 1 // The major device number for ramdisk is 1
#include "blk.h" // Block device related definitions
char *rd_start; // Pointer to the starting address of ramdisk in memory
int rd_length = 0; // Total length of ramdisk (in bytes)
/*
* Handle requests for the ramdisk
* Respond to read/write requests from the block device layer
*/
void do_rd_request(void)
{
int len; // Length of the current request
char *addr; // Pointer to the corresponding memory address in ramdisk
INIT_REQUEST; // Initialize request handling, check the validity of the current request
// Calculate the starting address in ramdisk: each sector is 512 bytes (left shift 9 is equivalent to multiplying by 512)
addr = rd_start + (CURRENT->sector << 9);
// Calculate the total length of the request: number of sectors × 512 bytes
len = CURRENT->nr_sectors << 9;
// Check if the device number is valid and if the requested address range exceeds the ramdisk range
if ((MINOR(CURRENT->dev) != 1) || (addr + len > rd_start + rd_length))
{
end_request(0); // Handling failed, notify the block device layer
goto repeat; // Continue processing the next request
}
// Perform the corresponding operation based on the request type
if (CURRENT->cmd == WRITE)
{
// Write operation: copy data from buffer to ramdisk
(void)memcpy(addr, CURRENT->buffer, len);
}
else if (CURRENT->cmd == READ)
{
// Read operation: copy data from ramdisk to buffer
(void)memcpy(CURRENT->buffer, addr, len);
}
else
// Unknown command, trigger kernel panic
panic("unknown ramdisk-command");
end_request(1); // Handling successful, notify the block device layer
goto repeat; // Continue processing the next request
}
/*
* Initialize the ramdisk
* Returns the amount of memory that needs to be reserved
* mem_start: starting memory address of the ramdisk
* length: length of the ramdisk
*/
long rd_init(long mem_start, int length)
{
int i;
char *cp;
// Register the request handling function for the ramdisk
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
rd_start = (char *)mem_start; // Set the starting address of the ramdisk
rd_length = length; // Set the length of the ramdisk
// Initialize all bytes of the ramdisk to 0
cp = rd_start;
for (i = 0; i < length; i++)
*cp++ = '\0';
return (length); // Return the amount of memory occupied by the ramdisk
}
/*
* If the root device is the ramdisk, try to load it
* To achieve this, the root device is initially set to the floppy,
* and we later change it to be the ramdisk
*/
void rd_load(void)
{
struct buffer_head *bh; // Buffer head pointer
struct super_block s; // Superblock structure
int block = 256; // Start reading from block 256 (location of ramdisk image on floppy)
int i = 1;
int nblocks; // Total number of blocks
char *cp; // Move pointer for copying data
// If the ramdisk length is 0, return directly (ramdisk not enabled)
if (!rd_length)
return;
// Print ramdisk information
printk("Ram disk: %d bytes, starting at 0x%x\n", rd_length,
(int)rd_start);
// If the current root device is not a floppy (major device number 2), return directly
if (MAJOR(ROOT_DEV) != 2)
return;
// Pre-read several blocks: block+1, block, block+2, to improve loading speed
bh = breada(ROOT_DEV, block + 1, block, block + 2, -1);
if (!bh)
{
printk("Disk error while looking for ramdisk!\n");
return;
}
// Read superblock information from the buffer
*((struct d_super_block *)&s) = *((struct d_super_block *)bh->b_data);
brelse(bh); // Release the buffer
// Check the magic number of the superblock to verify if it is a valid filesystem
if (s.s_magic != SUPER_MAGIC)
{
// No ramdisk image found, use normal floppy boot
return;
}
// Calculate total number of blocks: number of zones × number of blocks per zone (based on zone size logarithm)
nblocks = s.s_nzones << s.s_log_zone_size;
// Check if the ramdisk has enough space to accommodate the image
if (nblocks > (rd_length >>> BLOCK_SIZE_BITS))
{
printk("Ram disk image too big! (%d blocks, %d avail)\n",
nblocks, rd_length >>> BLOCK_SIZE_BITS);
return;
}
// Start loading the ramdisk image
printk("Loading %d bytes into ram disk... 0000k",
nblocks << BLOCK_SIZE_BITS);
cp = rd_start; // Point to the starting address of the ramdisk
// Loop to read all blocks
while (nblocks)
{
// If the remaining number of blocks is greater than 2, use pre-read function to improve efficiency
if (nblocks > 2)
bh = breada(ROOT_DEV, block, block + 1, block + 2, -1);
else
bh = bread(ROOT_DEV, block); // Read a single block
if (!bh)
{
printk("I/O error on block %d, aborting load\n", block);
return;
}
// Copy the read data to the ramdisk
(void)memcpy(cp, bh->b_data, BLOCK_SIZE);
brelse(bh); // Release the buffer
// Update progress display
printk("\010\010\010\010\010%4dk", i);
cp += BLOCK_SIZE; // Move the pointer in the ramdisk
block++; // Next block
nblocks--; // Decrease the remaining number of blocks by 1
i++;
}
// Loading complete
printk("\010\010\010\010\010done \n");
ROOT_DEV = 0x0101; // Change the root device to ramdisk (major device number 1, minor device number 1)
}
5. Source Code Image Version
6. Annotated Source Code Image Version
This series will provide detailed explanations of each file in the Linux 0.11 kernel, with a total of over 100 articles to be published.
😉【Linux102】1-Makefile
😉【Linux102】2-Makefile.header
😉【Linux102】3-system.map
😉【Linux102】4-bootsect.s
😉【Linux102】5-setup.s
😉【Linux102】6-head.s
😉【Linux102-D】/boot
😉【Linux102】7-main.c
😉【Linux102】8-kernel/asm.s
😉【Linux102】9-kernel/traps.c
😉【Linux102】10-kernel/printk.c
😉【Linux102】11-kernel/vsprintf.c
😉【Linux102】12-include/stdarg.h
😉【Linux102】13-kernel/mktime.c
😉【Linux102】14-kernel/system_call.s
😉【Linux102】15-include/linux/sched.h
😉【Linux102】16-kernel/sched.c
😉【Linux102】17-kernel/signal.c
😉【Linux102】18-include/signal.h
😉【Linux102】19-include/sys/types.h
😉【Linux102】20-include/linux/kernel.h
😉【Linux102】21-include/asm/segment.h
😉【Linux102】22-include/linux/head.h
😉【Linux102】23-include/linux/mm.h
😉【Linux102】24-include/linux/fs.h
😉【Linux102】25-include/errno.h
😉【Linux102】26-include/sys/wait.h
😉【Linux102】27-include/linux/tty.h
😉【Linux102】28-include/termios.h
😉【Linux102】29-kernel/panic.c
😉【Linux102】30-include/sys/times.h
😉【Linux102】31-include/sys/utsname.h
😉【Linux102】32-include/stddef.h
😉【Linux102】33-include/linux/sys.h
😉【Linux102】34-kernel/sys.c
😉【Linux102】35-kernel/fork.c
😉【Linux102】36-include/asm/system.h
😉【Linux102】37-kernel/exit.c
😉【Linux102】38-include/linux/fdreg.h
😉【Linux102】39-include/asm/io.h
😉【Linux102】40-kernel/blk_drv/blk.h
About Xiao Xi
😉 Hehe, I am Xiao Xi, focusing on the <span>Linux kernel</span> field, while also explaining <span>C language</span>, <span>assembly</span>, and other knowledge.
My WeChat: C_Linux_Cloud, looking forward to learning and communicating with you!
Please note when adding WeChat
Xiao Xi’s motto:
<span>Don't look at it as simple, simple is also difficult. Don't look at it as difficult, difficult is also simple.</span>My articles are all about explaining simple knowledge. If you like this style:
What do you want to see in the next issue? Leave a message in the comments! See you next time!