The original xv6-rev7 operating system contains:
12 directed blocks
1 indirect blcok(points to 128 blocks)
This means we have 140 blocks.
Each block's size is 512KB ==> 512 * 140 = 71,680 ~= 70KB is the limit of file's size in xv6.
I want to implemet triple indirect access in xv6 in order to support files with size of 40MB.
In order to do it I will need to implement double indirect before the triple indirect.
So I took 2 directed blocks from the 12 I had.
1 for the double indirect and the other for the triple indirect.
This is what I have right now:
Direct: 10 blocks
Single indirect: 128
Double indirect: 128*128
Triple indirect: 4*128*128 (I am using 4 instead of 128 because this is enough for 40MB)
This is why #define NDIRECT 10 and uint addrs[NDIRECT+3];
File's size limit = (10 + 128 + 128*128 + 4*128*128)*512kb = 42,013,696 ~= 42MB
So I understand the concept.
The implementation of the triple-indirection is in function bmap in file fs.c.
This is how it looks:
For some reason when I am trying to create file with size of 8.5MB it fails:
I am using bochs emulator
I am also not sure to what values I need to change in mkfs.c:
int nblocks = 20985;
int nlog = LOGSIZE;
int ninodes = 200;
int size = 21029;
fs.h:
// On-disk file system format.
// Both the kernel and user programs use this header file.
// Block 0 is unused.
// Block 1 is super block.
// Blocks 2 through sb.ninodes/IPB hold inodes.
// Then free bitmap blocks holding sb.size bits.
// Then sb.nblocks data blocks.
// Then sb.nlog log blocks.
#define ROOTINO 1 // root i-number
#define BSIZE 512 // block size
// File system super block
struct superblock {
uint size; // Size of file system image (blocks)
uint nblocks; // Number of data blocks
uint ninodes; // Number of inodes.
uint nlog; // Number of log blocks
};
#define NDIRECT 10
#define NINDIRECT (BSIZE / sizeof(uint))
#define MAXFILE (NDIRECT + NINDIRECT + NINDIRECT*NINDIRECT + 4*NINDIRECT*NINDIRECT)
// On-disk inode structure
struct dinode {
short type; // File type
short major; // Major device number (T_DEV only)
short minor; // Minor device number (T_DEV only)
short nlink; // Number of links to inode in file system
uint size; // Size of file (bytes)
uint addrs[NDIRECT+3]; // Data block addresses
};
// Inodes per block.
#define IPB (BSIZE / sizeof(struct dinode))
// Block containing inode i
#define IBLOCK(i) ((i) / IPB + 2)
// Bitmap bits per block
#define BPB (BSIZE*8)
// Block containing bit for block b
#define BBLOCK(b, ninodes) (b/BPB + (ninodes)/IPB + 3)
// Directory is a file containing a sequence of dirent structures.
#define DIRSIZ 14
struct dirent {
ushort inum;
char name[DIRSIZ];
};
fs.c:
// Return the disk block address of the nth block in inode ip.
// If there is no such block, bmap allocates one.
static uint
bmap(struct inode *ip, uint bn)
{
uint addr, *a;
struct buf *bp;
if(bn < NDIRECT){
if((addr = ip->addrs[bn]) == 0)
ip->addrs[bn] = addr = balloc(ip->dev);
return addr;
}
bn -= NDIRECT;
if(bn < NINDIRECT){
// Load indirect block, allocating if necessary.
if((addr = ip->addrs[NDIRECT]) == 0)
ip->addrs[NDIRECT] = addr = balloc(ip->dev);
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if((addr = a[bn]) == 0){
a[bn] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}
/* Double indirect */
bn -= NINDIRECT;
if(bn < NINDIRECT*NINDIRECT){
// Load 2nd indirect block, allocating if necessary.
if((addr = ip->addrs[NDIRECT+1]) == 0) // 2d block. NDIRECT+1 is to get the index vector
ip->addrs[NDIRECT+1] = addr = balloc(ip->dev);
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if ((addr = a[bn/(NINDIRECT)]) == 0) { /* get index for 1st
indirection. (NINDIRECT is 128) */
a[bn/(NINDIRECT)] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp); /* release the double indirect table
(main level) */
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if ((addr = a[bn%(NINDIRECT)]) == 0) { /* get the 2nd level table */
a[bn%(NINDIRECT)] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}
/* Triple indirect */
bn -= NINDIRECT*NINDIRECT;
if(bn < 4*NINDIRECT*NINDIRECT){
// Load 3rd indirect block, allocating if necessary.
if((addr = ip->addrs[NDIRECT+2]) == 0) // 3d block. NDIRECT+2 is to get the index vector
ip->addrs[NDIRECT+2] = addr = balloc(ip->dev);
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if ((addr = a[bn/(NINDIRECT*4)]) == 0) { /* get index for 2st
indirection. (NINDIRECT is 128) */
a[bn/(NINDIRECT*4)] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if ((addr = a[bn/(NINDIRECT*NINDIRECT*4)]) == 0) {
a[bn/(NINDIRECT*NINDIRECT*4)] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
if ((addr = a[bn%(NINDIRECT*NINDIRECT*4)]) == 0) {
a[bn%(NINDIRECT*NINDIRECT*4)] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}
panic("bmap: out of range");
}
mkfs.c:
#define stat xv6_stat // avoid clash with host struct stat
#include "types.h"
#include "fs.h"
#include "stat.h"
#include "param.h"
int nblocks = 20985;
int nlog = LOGSIZE;
int ninodes = 200;
int size = 21029;
bigfile.c:
#include "types.h"
#include "stat.h"
#include "user.h"
#include "fcntl.h"
void
help()
{
printf(1, "usage:\nfiles <name> <letter> <num>\n"
"e.g. nfiles foo a 40\n creates a file foo, with 40 times the letter a\n");
}
void
num2str(int i, char str[3])
{
str[2]=i%10+'0';
i=i/10;
str[1]=i%10+'0';
i=i/10;
str[0]=i%10+'0';
i=i/10;
}
#define BUF_SZ 512
int
main(int argc, char *argv[])
{
int i, count, fd, n;
// char *name;
// char c;
char buf[BUF_SZ];
if (argc !=4) {
help();
exit();
}
count = atoi(argv[3]);
if((fd=open(argv[1], O_CREATE|O_RDWR))<0) {
printf(2,"Failed to open file: %s\n", argv[1]);
exit();
}
for (i=0; i<BUF_SZ;i++)
buf[i]=argv[2][0];
for (i=0; i<count/BUF_SZ;i++)
if ((n=write(fd,buf,BUF_SZ)) != BUF_SZ)
{
printf(2,"Failed 1 to Write count=%d\n",i*BUF_SZ);
exit();
}
for (i=0; i<count%BUF_SZ;i++)
if ((n=write(fd,argv[2],1)) != 1)
{
printf(2,"Failed 2 to Write count=%d\n",count-i);
exit();
}
exit();
}
1.The number of nblocks which is defined in mkfs.c is insufficient.
int nblocks = 20985;
int nlog = LOGSIZE;
int ninodes = 200;
int size = 21029;
You have defined:
#define MAXFILE (NDIRECT + NINDIRECT + NINDIRECT*NINDIRECT + 4*NINDIRECT*NINDIRECT
which equals: 10+128+128^2+4*128^2 = 82058.
Just pick a number of nblocks which is greater than 82058, and update size accordingly.
2.In your bmap() function, in the triple indirection code, your first level of indirection is to a four-entry array (as you mentioned in your diagram).
Once you know to which of these four entries you should access, you're back with the double indirection problem, which you already solved.
So, in order to know which of the four entries you should access, you can use this:
if((addr = a[bn/(NINDIRECT*NINDIRECT)]) == 0){
a[bn/(NINDIRECT*NINDIRECT)] = addr = balloc(ip->dev);
log_write(bp);
}
You then could decrease bn like so:
bn -= ((NINDIRECT*NINDIRECT)*(bn/(NINDIRECT*NINDIRECT)));
and solve the double indirection problem again.
Related
I must further develop a simulator using C, capable of simulating different cache types (direct, n-way associative, fully associative). Right now my code works in the sense that it can simulate a direct-mapped cache, however it cannot simulate any other type.
My Code
My C file:
/*
* CS3375 Computer Architecture
* Course Project
* Cache Simulator Design and Development
* FALL 2017
* By Yong Chen
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include "cachesim.h"
int main(int argc, char *argv[])
{
char type;
if (argc != 3) {
printf("Usage: %s <direct> <trace file name>\n", argv[0]);
return 1;
}
#ifdef DBG
printf("BLOCK SIZE = %d Bytes\n", BLOCK_SIZE);
printf("%d-WAY\n", WAY_SIZE);
printf("CACHE SIZE = %d Bytes\n", CACHE_SIZE);
printf("NUMBER OF BLOCKS = %d\n", NUM_BLOCKS);
printf("NUMBER OF SETS = %d\n", NUM_SETS);
printf("\n");
#endif
struct direct_mapped_cache d_cache;
char* trace_file_name = argv[2];
char mem_request[20];
uint64_t address;
FILE *fp;
/* Initialization */
for (int i=0; i<NUM_BLOCKS; i++) {
d_cache.valid_field[i] = 0;
d_cache.dirty_field[i] = 0;
d_cache.tag_field[i] = 0;
}
d_cache.hits = 0;
d_cache.misses = 0;
/* Opening the memory trace file */
fp = fopen(trace_file_name, "r");
/*Checks if argument specified direct-mapped cache*/
if (strncmp(argv[1], "direct", 6)==0) { /* Simulating direct-mapped cache */
/* Read the memory request address and access the cache */
while (fgets(mem_request, 20, fp)!= NULL) {
address = convert_address(mem_request);
direct_mapped_cache_access(&d_cache, address);
}
/*Calculate Hit and Miss Rate*/
double hit_rate = ((1.0 * d_cache.hits)/(d_cache.hits + d_cache.misses));
double miss_rate = ((1.0 * d_cache.misses)/(d_cache.hits + d_cache.misses));
/*Print out the results*/
printf("\n==================================\n");
printf("Cache type: Direct-Mapped Cache\n");
printf("==================================\n");
printf("Cache Hits: %d\n", d_cache.hits);
printf("Cache Misses: %d\n", d_cache.misses);
printf("Cache Hit Rate: %f\n", hit_rate);
printf("Cache Miss Rate: %f\n", miss_rate);
printf("\n");
}
fclose(fp);
return 0;
}
uint64_t convert_address(char memory_addr[])
/* Converts the physical 32-bit address in the trace file to the "binary" \\
* (a uint64 that can have bitwise operations on it) */
{
uint64_t binary = 0;
int i = 0;
while (memory_addr[i] != '\n') {
if (memory_addr[i] <= '9' && memory_addr[i] >= '0') {
binary = (binary*16) + (memory_addr[i] - '0');
} else {
if(memory_addr[i] == 'a' || memory_addr[i] == 'A') {
binary = (binary*16) + 10;
}
if(memory_addr[i] == 'b' || memory_addr[i] == 'B') {
binary = (binary*16) + 11;
}
if(memory_addr[i] == 'c' || memory_addr[i] == 'C') {
binary = (binary*16) + 12;
}
if(memory_addr[i] == 'd' || memory_addr[i] == 'D') {
binary = (binary*16) + 13;
}
if(memory_addr[i] == 'e' || memory_addr[i] == 'E') {
binary = (binary*16) + 14;
}
if(memory_addr[i] == 'f' || memory_addr[i] == 'F') {
binary = (binary*16) + 15;
}
}
i++;
}
#ifdef DBG
printf("%s converted to %llu\n", memory_addr, binary);
#endif
return binary;
}
void direct_mapped_cache_access(struct direct_mapped_cache *cache, uint64_t address)
{
uint64_t block_addr = address >> (unsigned)log2(BLOCK_SIZE);
uint64_t index = block_addr % NUM_BLOCKS;
uint64_t tag = block_addr >> (unsigned)log2(NUM_BLOCKS);
#ifdef DBG
printf("Memory address: %llu, Block address: %llu, Index: %llu, Tag: %llu ", address, block_addr, index, tag);
#endif
if (cache->valid_field[index] && cache->tag_field[index] == tag) { /* Cache hit */
cache->hits += 1;
#ifdef DBG
printf("Hit!\n");
#endif
} else {
/* Cache miss */
cache->misses += 1;
#ifdef DBG
printf("Miss!\n");
#endif
if (cache->valid_field[index] && cache->dirty_field[index]) {
/* Write the cache block back to memory */
}
cache->tag_field[index] = tag;
cache->valid_field[index] = 1;
cache->dirty_field[index] = 0;
}
}
My .h file:
/*
* CS3375 Computer Architecture
* Course Project
* Cache Simulator Design and Development
* FALL 2017
* By Yong Chen
*/
#include <stdio.h>
/* Cache block size (or cache line size) in bytes*/
#define BLOCK_SIZE 64 /*(must be power of 2). 4 Bytes = 1 Word NOTE: MUST CHANGE DEPENDING ON TYPE*/
#define WAY_SIZE 1 /* Associativity; 1-way = direct-mapped MUST CHANGE DEPENDING ON TYPE*/
#define CACHE_SIZE 32768 /* Cache capacity in bytes (must be power of 2) THIS WILL STAY FIXED*/
#define NUM_BLOCKS (CACHE_SIZE / BLOCK_SIZE)
#define NUM_SETS (BLOCK_SIZE/WAY_SIZE)
/*For fully associative, num sets is equal to num blocks because way size is equal to num blocks. */
/*MAY TRY LEAVING THESE VARIABLES UNDEFINED, AND THEY WILL BE SET DEPENDING ON USER INPUT.*/
#define DBG /*Prints debugging information*/
/*The data structure of direct-mapped cache*/
struct direct_mapped_cache {
unsigned valid_field[NUM_BLOCKS]; /* Valid field */
unsigned dirty_field[NUM_BLOCKS]; /* Dirty field; since we don't distinguish writes and \\
reads in this project yet, this field doesn't really matter */
uint64_t tag_field[NUM_BLOCKS]; /* Tag field */
char data_field[NUM_BLOCKS][BLOCK_SIZE]; /* Data field; since we don't really fetch data, \\
this field doesn't really matter */
int hits; /* Hit count */
int misses; /* Miss count */
};
/*Read the memory traces and convert it to binary*/
uint64_t convert_address(char memory[]);
/*Simulate the direct-mapped cache*/
void direct_mapped_cache_access(struct direct_mapped_cache *cache, uint64_t address);
What I've Tried
Admittedly, I am a beginner when it comes to the C language, so my solution may be simpler than I think, but I've been unable to find any answers thus far. I've considered changing where the cache variables were defined using "#define" depending on the argument, but I have learned that "#define" is run by pre-processing, so this won't work.
I've also tried creating multiple struct classes for each type of cache that needs to be simulated, but since struct variables in C cannot be initialized within the class, I can't get this to work either.
To my understanding, structs in C cannot have constructors either, as I've looked into this as well.
Any help or step in the right direction will be greatly appreciated.
first there is a you are mistaken about struct : they are not object but a data layout which mean no constructor or destructor (if you need those feature you will need dedicated function)
next your actual question: "can i have a variable amount of memory in my struct ?"
Answer:
Yes, but not directly, you will need to use function like malloc and free to dynamically allocate and deallocate memory, i won't make a full explanation about them you will find how to use them very easily online.
You will need to do something like :
#include<stdlib.h>
struct mystruct {
unsigned size;
unsigned * memory;
};
typedef struct mystruct mystruct;
mystruct * mystruct_constructor(unsigned s) {
//allocate the space of struct itself
mystruct * ms = malloc(sizeof(mystruct));
ms->size = s;
//allocate the variable size array
ms->memory = malloc(sizeof(unsigned) * s);
return ms;
}
void mystruct_destructor(mystruct * ms) {
free(ms->memory);
free(ms);
}
just know that malloc don't initialize the space so the data within are unknown (and likely not zero).
as for the why you can't define variable size struct is because you need compile time array (those like unsigned var[X]) to have a compile time defined size, because when you make such array the struct actually contain X element of the array type.
which mean:
#define N whatever_value_you_want
struct {
unsigned item[N + 1]; // valid because it can be known at compile-time
};
has the same (under normal condition) layout as:
struct {
unsigned item_0;
unsigned item_1;
unsigned item_2;
...
unsigned item_N;
}
I wrote the below program to iterate over all images in memory and dump their string tables.
#include <mach-o/dyld.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv) {
uint32_t count = _dyld_image_count();
for (uint32_t i = 0 ; i < count ; i++) {
const char* imageName = _dyld_get_image_name(i);
printf("IMAGE[%u]=%s\n", i, imageName);
const struct mach_header* header = _dyld_get_image_header(i);
if (header->magic != MH_MAGIC_64)
continue;
struct mach_header_64* header64 = (struct mach_header_64*)header;
char *ptr = ((void*)header64) + sizeof(struct mach_header_64);
for (uint32_t j = 0; j < header64->ncmds; j++) {
struct load_command *lc = (struct load_command *)ptr;
ptr += lc->cmdsize;
if (lc->cmd != LC_SYMTAB)
continue;
struct symtab_command* symtab = (struct symtab_command*)lc;
printf("\t\tLC_SYMTAB.stroff=%u\n", symtab->stroff);
printf("\t\tLC_SYMTAB.strsize=%u\n", symtab->strsize);
if (symtab->strsize > 100*1024*1024) {
printf("\t\tHUH? Don't believe string table is over 100MiB in size!\n");
continue;
}
char *strtab = (((void*)header64) + symtab->stroff);
uint32_t off = 0;
while (off < symtab->strsize) {
char *e = &(strtab[off]);
if (e[0] != 0)
printf("\t\tSTR[%u]=\"%s\"\n", off, e);
off += strlen(e) + 1;
}
}
}
return 0;
}
It seems to randomly work for some images, but for others the stroff/strsize have nonsensical values:
LC_SYMTAB.stroff=1266154560
LC_SYMTAB.strsize=143767728
It seems to always be the same two magic values, but I'm not sure if this is system-dependent in some way or if other people will get the same specific values.
If I comment out the check for strsize being over 100MiB, then printing the string table segfaults.
Most images seem to have this problem, but some don't. When I run it, I get the issue for 29 images out of 38.
I can't observe any pattern as to which do and which won't. What is going on here?
If it is relevant, I am testing on macOS 10.14.6 and compiling with Apple LLVM version 10.0.1 (clang-1001.0.46.4).
As you already worked out, those are from the dyld_shared_cache. And the 0x80000000 flag is indeed documented, in the headers shipped with Xcode or any semi-recent XNU source:
#define MH_DYLIB_IN_CACHE 0x80000000 /* Only for use on dylibs. When this bit
is set, the dylib is part of the dyld
shared cache, rather than loose in
the filesystem. */
As you've also discovered, the stroff/strsize values do not yield usable results when added to the dyld_shared_cache base. That is because those are not memory offsets, but file offsets. This is true for all Mach-O's, it's just often the case that the segments of non-cached binaries have the same relative position in file and memory offsets. But this is definitely not true for the shared cache.
To translate the file offset into a memory address, you'll have to parse the segments in the shared cache header. You can find struct definitions in the dyld source.
Here's a program which prints out the contents of the string table of the dyld shared cache.
My original program in the question can be enhanced to skip dumping string table of images with MH_DYLIB_IN_CACHE set, and combined with this program to dump the shared cache string table. (All images in the shared cache share the same string table.)
#include <mach-o/dyld.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
const void* _dyld_get_shared_cache_range(size_t* cacheLen);
struct dyld_cache_header {
char magic[16];
uint32_t mappingOffset;
uint32_t mappingCount;
// Omitted remaining fields, not relevant to this task
};
struct dyld_cache_mapping_info {
uint64_t address;
uint64_t size;
uint64_t fileOffset;
uint32_t maxProt;
uint32_t initProt;
};
#ifndef MH_DYLIB_IN_CACHE
# define MH_DYLIB_IN_CACHE 0x80000000
#endif
// Finds first shared cache DYLD image. Any will do, just grab the first
const struct mach_header_64* findSharedCacheDyldImage(void) {
uint32_t count = _dyld_image_count();
for (uint32_t i = 0 ; i < count ; i++) {
const struct mach_header* header = _dyld_get_image_header(i);
if (header->magic != MH_MAGIC_64)
continue;
const struct mach_header_64* header64 = (const struct mach_header_64*)header;
if (!(header64->flags & MH_DYLIB_IN_CACHE))
continue;
return header64;
}
return NULL;
}
// Find first instance of given load command in image
const struct load_command* findFirstLoadCommand(const struct mach_header_64* header64, uint32_t cmd) {
const char *ptr = ((void*)header64) + sizeof(struct mach_header_64);
for (uint32_t j = 0; j < header64->ncmds; j++) {
const struct load_command *lc = (const struct load_command *)ptr;
ptr += lc->cmdsize;
if (lc->cmd == cmd)
return lc;
}
return NULL;
}
// Translates a shared cache file offset to a memory address
void *translateOffset(const struct dyld_cache_header *cache, uint64_t offset) {
const struct dyld_cache_mapping_info* mappings = (struct dyld_cache_mapping_info*)(((void*)cache) + cache->mappingOffset);
for (uint32_t i = 0; i < cache->mappingCount; i++) {
if (offset < mappings[i].fileOffset) continue;
if (offset >= (mappings[i].fileOffset + mappings[i].size)) continue;
return (void*)(mappings[i].address - mappings[0].address + (offset - mappings[i].fileOffset) + (uint64_t)cache);
}
return NULL;
}
int main(int argc, char** argv) {
size_t cacheLen;
const struct dyld_cache_header *cache = _dyld_get_shared_cache_range(&cacheLen);
const struct mach_header_64* sharedCacheDyldImage = findSharedCacheDyldImage();
const struct symtab_command* symtab = (const struct symtab_command*)findFirstLoadCommand(sharedCacheDyldImage,LC_SYMTAB);
const void *stringTbl = translateOffset(cache, symtab->stroff);
uint32_t off = 0;
while (off < symtab->strsize) {
const char *e = &(stringTbl[off]);
if (e[0] != 0)
printf("STR[%u]=\"%s\"\n", off, e);
off += strlen(e) + 1;
}
return 0;
}
This is the old task of implementing double-indirect inodes for the xv6 system. In double indirect a file can have 16523 blocks (11 direct-pointer + 1 single-indirect-pointer*128 + 1 double-indirect-pointer*128*128), and my implementation satisfies this condition according to the usertests output (I will present it below).
The full code of the xv6 system can be found at https://github.com/mit-pdos/xv6-public, and since if you are here you probably familiar with it, I will only present the modifications I made in the implementation of bmap() and itrunc() methods at fs.c:
// NINDIRECT is 128
// NDIRECT is 11
// There is one less direct pointer to make room for the double-indirect
// Return the disk block address of the nth block in inode ip.
// If there is no such block, bmap allocates one.
static uint
bmap(struct inode *ip, uint bn)
{
uint addr, *a;
uint *indirect;
struct buf *bp;
struct buf *dbp;
// Direct Pointer
if(bn < NDIRECT){
// get direct pointer, allocate if necessary
if((addr = ip->addrs[bn]) == 0)
ip->addrs[bn] = addr = balloc(ip->dev);
return addr;
}
bn -= NDIRECT;
// Indirect Pointer
if(bn < NINDIRECT){
// load indirect block, allocate if necessary
if((addr = ip->addrs[NDIRECT]) == 0)
ip->addrs[NDIRECT] = addr = balloc(ip->dev);
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
// get direct pointer, allocate if necessary
if((addr = a[bn]) == 0){
a[bn] = addr = balloc(ip->dev);
log_write(bp);
}
// release indirect block
brelse(bp);
// return disk block address
return addr;
}
bn -= NINDIRECT;
// Double-indirect Pointer
if(bn < NDINDIRECT){
// load double-indirect block, allocate if necessary
if((addr = ip->addrs[NDIRECT+1]) == 0)
ip->addrs[NDIRECT] = addr = balloc(ip->dev);
dbp = bread(ip->dev,addr);
indirect = (uint*)dbp->data;
// load indirect block, allocate if necessary
if((addr = indirect[bn/NINDIRECT]) == 0)
indirect[bn/NINDIRECT] = addr = balloc(ip->dev);
bp = bread(ip->dev,addr);
a = (uint*)bp->data;
// get direct pointer, allocate if necessary
if((addr = a[bn%NINDIRECT]) == 0){
a[bn%NINDIRECT] = addr = balloc(ip->dev);
log_write(bp);
}
// release indirect & double-indirect blocks
brelse(bp);
brelse(dbp);
// return disk block address
return addr;
}
panic("bmap: out of range");
}
// Truncate inode (discard contents).
// Only called when the inode has no links
// to it (no directory entries referring to it)
// and has no in-memory reference to it (is
// not an open file or current directory).
static void
itrunc(struct inode *ip)
{
int i, j, k;
struct buf *bp;
struct buf *dbp;
uint *a;
uint *d;
// free the direct pointers
for(i = 0; i < NDIRECT; i++){
if(ip->addrs[i]){
bfree(ip->dev, ip->addrs[i]);
ip->addrs[i] = 0;
}
}
// free the indirect pointer
if(ip->addrs[NDIRECT]){
// load the indirect block
bp = bread(ip->dev, ip->addrs[NDIRECT]);
a = (uint*)bp->data;
// iterate over the block, and free all direct pointers
for(j = 0; j < NINDIRECT; j++){
if(a[j])
bfree(ip->dev, a[j]);
}
// free the indirect pointer
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT]);
ip->addrs[NDIRECT] = 0;
}
// free the double indirect pointer
if(ip->addrs[NDIRECT+1]){
// load the double-indirect block
dbp = bread(ip->dev, ip->addrs[NDIRECT+1]);
d = (uint*)dbp->data;
// iterate over the block, and free all indirect blocks
for(k = 0; k < NINDIRECT; k++){
// load the indirect block
bp = bread(ip->dev, d[k]);
a = (uint*)bp->data;
// iterate over the block, and free all direct pointers
for(j = 0; j < NINDIRECT; j++){
if(a[j])
bfree(ip->dev,a[j]);
}
// free the infirect pointer
brelse(bp);
bfree(ip->dev, d[k]);
d[k] = 0;
}
// free the double-indirect pointer
brelse(dbp);
bfree(ip->dev, ip->addrs[NDIRECT+1]);
ip->addrs[NDIRECT+1] = 0;
}
ip->size = 0;
iupdate(ip);
}
The output I get from the usertests utility shows that the writing part is successful, but the system panic when it attempt to read the big file:
wrote 16523 sectors
closed the file for writing
opend the file for reading
read 0 sectors
lapicid 1: panic: log_write outside of trans
80102f18 80101249 80101442 80101b67 80100f89 80104b42 8010482a 80105829 80105572 0
I modified the bigfile() test a bit, so I will post it here too:
void
bigfile(void)
{
char buf[512];
int fd, i, sectors;
unlink("big.file");
fd = open("big.file", O_CREATE | O_WRONLY);
if(fd < 0){
printf(2, "big: cannot open big.file for writing\n");
exit();
}
sectors = 0;
while(1){
*(int*)buf = sectors;
int cc = write(fd, buf, sizeof(buf));
if(cc <= 0)
break;
sectors++;
if (sectors % 100 == 0){
printf(2, ".");
printf(2,"sector: %d\n",sectors);
}
}
printf(1, "\nwrote %d sectors\n", sectors);
close(fd);
printf(1,"closed the file for writing\n");
fd = open("big.file", O_RDONLY);
if(fd < 0){
printf(2, "big: cannot re-open big.file for reading\n");
exit();
}
printf(1,"opend the file for reading\n");
for(i = 0; i < sectors; i++){
int cc = read(fd, buf, sizeof(buf));
if(cc <= 0){
printf(2, "big: read error at sector %d\n", i);
exit();
}
if(*(int*)buf != i){
printf(2, "big: read the wrong data (%d) for sector %d\n",
*(int*)buf, i);
exit();
}
if(i%100 == 0)
printf(2,"read %d sectors\n",i);
}
close(fd);
unlink("big.file");
printf(1, "bigfile test ok\n");
}
Thanks in advance for any help.
P.S: of course I modified the size of FSSIZE in the param.h file to be big enough for double indirect pointers.
Context: I have written a best-fit memory allocator. It allocates large blocks of memory, and serves best fitting chunks of it to programs upon request. If memory reserves are exhausted, its asks for OS for more. All free blocks are stored on a linked-list, ordered by increasing pointer value.
Problem:: When memory is released, the program is supposed to link it back into the free list of blocks for recycling, and more crucially merges the given block with its surrounding blocks if possible. Unfortunately, this only works well as long as I do not need to ask the OS for more then one super-block to serve. When this does occur, the new blocks I receive have nonsensical addressing and get inserted between other super-block addressing space. This leads to permanent fragmentation.
Tl;dr: I am being given new super-blocks of memory whose address is within the address space of another super-block, leading to fragmentation when sub-blocks are returned.
Illustration of problem:
Here is a diagram of the problem I described above.
Numbers: Memory addresses (from real execution).
Beige blocks: Free memory.
White blocks: Memory unlinked for use.
The diagram shows the progression of memory usage until the fragmentation catalyst occurs. You can see once the block gets inserted, merging will never be possible up for the busy blocks once they are checked back in.
Code: Reproducible Example:
The following includes the allocator and a small test program. It must be compiled with at least C99.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/*****************************************************************************/
/* SYMBOLIC CONSTANTS */
/*****************************************************************************/
#define NEXT(hp) ((hp)->key.next)
#define UNITS(hp) ((hp)->key.units)
#define UNIT_SIZE sizeof(Header)
#define MIN_UNITS_ALLOC 64
#define MAX(a,b) ((a) > (b) ? (a) : (b))
/*****************************************************************************/
/* TYPE DEFINITIONS */
/*****************************************************************************/
typedef union header {
intmax_t align;
struct {
union header *next;
unsigned units;
} key;
} Header;
/*****************************************************************************/
/* GLOBAL VARIABLES */
/*****************************************************************************/
Header base = {.key = { NULL, 0 }};
Header *list;
/*****************************************************************************/
/* PROTOTYPES */
/*****************************************************************************/
/* Allocates a 'bytes' size block of memory. On success, returns pointer to
* the block. On error, NULL is returned. */
void *alloc (size_t bytes);
/* Returns a 'bytes' size block of allocated memory for reuse */
void release (void *bytes);
/* Attempts to reserve memory from the operating system via a system call */
static Header *reserve (unsigned units);
/*****************************************************************************/
/* FUNCTION IMPLEMENTATIONS */
/*****************************************************************************/
void *alloc (size_t bytes) {
size_t units, diff;
Header *block, *lastBlock, *best, *lastBest;
if (bytes == 0) {
return NULL;
}
if (list == NULL) {
list = base.key.next = &base;
}
best = lastBest = NULL;
units = (bytes + UNIT_SIZE - 1) / UNIT_SIZE + 1;
diff = SIZE_MAX;
for (lastBlock = list, block = NEXT(list); ; lastBlock = block, block = NEXT(block)) {
/* Loop across list, find closest fitting block */
if (UNITS(block) >= units && UNITS(block) - units < diff) {
diff = UNITS(block) - units;
best = block;
lastBest = lastBlock;
}
/* Upon cycle completion */
if (block == list) {
/* If no block available, reserve some. */
if (best == NULL) {
if ((lastBest = reserve(units)) == NULL) {
return NULL;
} else {
fprintf(stderr, "\nalloc: Out of memory, linking new block %lld of size %u.\n\n", (long long)lastBest, UNITS(lastBest));
release((void *)(lastBest + 1));
}
/* If block of perfect size, return. Else slice and return */
} else {
if (diff == 0) {
NEXT(lastBest) = NEXT(best);
} else {
UNITS(best) = diff;
best += diff;
UNITS(best) = units;
}
fprintf(stderr, "alloc: Unlinked block %lld of %u units.\n", (long long)best, UNITS(best));
return (void *)(best + 1);
}
}
}
}
void release (void *bytes) {
Header *p, *block;
block = (Header *)bytes - 1;
/* Choose p such that: p -> block -> NEXT(p) */
for (p = list; !(p < block && NEXT(p) > block); p = NEXT(p)) {
if (p >= NEXT(p) && (block > p || block < NEXT(p))) {
break;
}
}
/* Merge block with NEXT(p) if adjacent */
if (block + UNITS(block) == NEXT(p)) {
NEXT(block) = NEXT(NEXT(p));
UNITS(block) += UNITS(NEXT(p));
} else {
NEXT(block) = NEXT(p);
}
/* Merge block with p if adjacent */
if (p + UNITS(p) == block) {
NEXT(p) = NEXT(block);
UNITS(p) += UNITS(block);
} else {
NEXT(p) = block;
}
}
static Header *reserve (unsigned units) {
char *bytes, *sbrk(int);
Header *block;
units = MAX(units, MIN_UNITS_ALLOC);
if ((bytes = sbrk(units)) == (char *)-1) {
return NULL;
} else {
block = (Header *)bytes;
UNITS(block) = units;
}
return block;
}
/*****************************************************************************/
/* TESTING FUNCTIONS (DELETE) */
/*****************************************************************************/
void printFreeList (unsigned byAddress) {
Header *lp = list;
if (lp == NULL) {
fprintf(stdout, "List is NULL\n");
return;
}
do {
fprintf(stdout, "[ %lld ] -> ", (byAddress ? (long long)lp : (long long)UNITS(lp)));
lp = NEXT(lp);
} while (lp != list);
putc('\n', stdout);
}
void releaseItemAtIndex (int i, int k, long long *p[]) {
fprintf(stderr, "Releasing block %d/%d\n", i, k);
release(p[i]);
for (int j = i; j < k; j++) {
if (j + 1 < k) {
p[j] = p[j + 1];
}
}
}
#define MAX_TEST_SIZE 5000
int main (int argc, const char *argv[]) {
/* Seed PRNG: For removed random deletion (now manual) */
srand(time(NULL));
/* Array of pointers to store allocated blocks, blockSize we want to allocate. */
long long *p[MAX_TEST_SIZE], blockSize = 256;
/* Number of blocks we choose to allocate */
int k = 6;
/* Allocate said blocks */
for (int i = 0; i < k; i++) {
p[i] = alloc(blockSize * sizeof(char));
printFreeList(0); printFreeList(1); putchar('\n');
}
fprintf(stderr, "\n\n");
int idx;
while (k > 0) {
fprintf(stderr, "Delete an index between 0 up to and including %d:\n", k - 1);
scanf("%d", &idx);
releaseItemAtIndex(idx, k, p);
printFreeList(0); printFreeList(1); putchar('\n');
k--;
}
return 0;
}
Miscellaneous Details:
I am running a 64 bit operating system.
I do not know if pointer comparison on the heap is guaranteed to be valid. This is not guaranteed by the standard according to K&R.
Well I eventually wrote one a couple years later that seems to work alright. Bonus: It's using static memory.
Header File
#if !defined(STATIC_ALLOCATOR_H)
#define STATIC_ALLOCATOR_H
/*
*******************************************************************************
* (C) Copyright 2020 *
* Created: 07/04/2020 *
* *
* Programmer(s): *
* - Jillian Oduber *
* - Charles Randolph *
* *
* Description: *
* Static first-fit memory allocator *
* *
*******************************************************************************
*/
#include <iostream>
#include <cstddef>
#include <cstdint>
#include "dx_types.h"
extern "C" {
#include "stddef.h"
}
/*
*******************************************************************************
* Type Definitions *
*******************************************************************************
*/
// Defines the header block used in the custom allocator
typedef union block_h {
struct {
union block_h *next;
size_t size; // Size is in units of sizeof(block_h)
} d;
max_align_t align;
} block_h;
/*
*******************************************************************************
* Class Definitions *
*******************************************************************************
*/
class Static_Allocator {
public:
/*\
* #brief Configures the class with the given static memory capacity
* #param memory Pointer to static array (must support address comparison)
* #param size Total number of bytes alloted for distribution
\*/
Static_Allocator(uint8_t *memory, size_t size);
/*\
* #brief Returns a memory block of the desired size
* #param size Number of bytes to allocate
* #return
* - valid pointer if memory is available
* - NULL otherwise
\*/
uint8_t *alloc (size_t size);
/*\
* #brief Releases the allocated block of memory
* #param ptr Pointer to allocated block of memory
* #return
* - STATUS_OK on success
* - STATUS_ERR if invalid block
* - STATUS_BAD_PARAM on NULL parameter
\*/
dx::status_t free (uint8_t *ptr);
/*\
* #brief Returns the amount of free memory
* #return size_t Bytes (of available free memory)
\*/
size_t free_memory_size ();
/*\
* #brief Debug utility which shows what is on the free-list
* #return None
\*/
void show ();
/*\
* #brief Debug utility which asserts there is only a single
* memory block
\*/
bool unified ();
private:
// Fixed allocator capacity
size_t d_capacity;
// Fixed memory
uint8_t *d_memory;
// Pointer to head of the linked list
block_h *d_free_list;
// Available memory
size_t d_free_memory_size;
// Unit size
static const size_t mem_unit_size = sizeof(block_h);
};
#endif
Source File
#include "static_allocator.h"
Static_Allocator::Static_Allocator (uint8_t *memory, size_t size):
d_capacity(0),
d_memory(NULL),
d_free_list(NULL),
d_free_memory_size(0)
{
// Assign memory array
d_memory = memory;
// Trim off two mem_unit_size for head + init-block + spillover
d_capacity = size - (size % mem_unit_size) - 2 * mem_unit_size;
// Ensure free memory is set to capacity
d_free_memory_size = d_capacity;
}
uint8_t * Static_Allocator::alloc (size_t size)
{
block_h *last, *curr;
// Number of blocks sized units to allocate
size_t nblocks = (size + mem_unit_size - 1) / mem_unit_size + 1;
// If larger than total capacity or invalid size; immediately return
if ((nblocks * sizeof(block_h)) > d_capacity || size == 0) {
return NULL;
}
// If uninitialized, create initial list units
if ((last = d_free_list) == NULL) {
block_h *head = reinterpret_cast<block_h *>(d_memory);
head->d.size = 0;
block_h *init = head + 1;
init->d.size = d_capacity / sizeof(block_h);
init->d.next = d_free_list = last = head;
head->d.next = init;
}
// Problem: You must not be allowed to merge the init block
// Look for space - stop if wrapped around
for (curr = last->d.next; ; last = curr, curr = curr->d.next) {
// If sufficient space available
if (curr->d.size >= nblocks) {
if (curr->d.size == nblocks) {
last->d.next = curr->d.next;
} else {
curr->d.size -= nblocks;
curr += curr->d.size; // Suspect
curr->d.size = nblocks;
}
// Reassign free list head
d_free_list = last;
// Update amount of free memory available
d_free_memory_size -= nblocks * sizeof(block_h);
return reinterpret_cast<uint8_t *>(curr + 1);
}
// Otherwise not sufficient space. If reached
// the head of the list again, there is no more
// memory left
if (curr == d_free_list) {
return NULL;
}
}
}
dx::status_t Static_Allocator::free (uint8_t *ptr)
{
block_h *b, *p;
// Check if parameter is valid
if (ptr == NULL) {
std::cerr << "Null pointer!";
return dx::STATUS_BAD_PARAM;
}
// Check if memory is in range
if (!(ptr >= (d_memory + 2 * mem_unit_size)
&& ptr < (d_memory + d_capacity + 2 * mem_unit_size))) {
std::cerr << "Pointer out of range!";
return dx::STATUS_ERR;
}
// Obtain block header (ptr - sizeof(block_h))
b = reinterpret_cast<block_h *>(ptr) - 1;
// Update available memory size
d_free_memory_size += b->d.size;
// Find insertion location for block
for (p = d_free_list; !(b >= p && b < p->d.next); p = p->d.next) {
// If the block comes at the end of the list - break
if (p >= p->d.next && b > p) {
break;
}
// If at the end of the list, but block comes before next link
if (p >= p->d.next && b < p->d.next) {
break;
}
}
// [p] <----b----> [p->next] ----- [X]
// Check if we can merge forwards
if (b + b->d.size == p->d.next) {
b->d.size += (p->d.next)->d.size;
b->d.next = (p->d.next)->d.next;
} else {
b->d.next = p->d.next;
}
// Check if we can merge backwards
if (p + p->d.size == b) {
p->d.size += b->d.size;
p->d.next = b->d.next;
} else {
p->d.next = b;
}
d_free_list = p;
return dx::STATUS_OK;
}
size_t Static_Allocator::free_memory_size ()
{
return d_free_memory_size;
}
void Static_Allocator::show ()
{
std::cout << "Block Size: " << sizeof(block_h) << "\n"
<< "Capacity: " << d_capacity << "\n"
<< "Free Size (B): " << d_free_memory_size << "\n";
if (d_free_list == NULL) {
std::cout << "<uninitialized>\n";
return;
}
// Show all blocks
block_h *p = d_free_list;
do {
std::cout << "-------------------------------\n";
std::cout << "Block Address: " << (uint64_t)(p) << "\n";
std::cout << "Blocks (32B): " << p->d.size << "\n";
std::cout << "Next: " << (uint64_t)(p->d.next) << "\n";
std::cout << "{The next block is " << (uint64_t)((p->d.next) - (p)) << " bytes away, but we claim the next " << p->d.size * mem_unit_size << " bytes\n";
p = p->d.next;
} while (p != d_free_list);
std::cout << "-------------------------------\n\n\n";
}
bool Static_Allocator::unified () {
if (d_free_list == NULL) {
return false;
}
return ((d_free_list->d.next)->d.next == d_free_list);
}
I have a hex file of 327680 characters which I'm writing to physical address 0x30000000 - 0x3004FFFF on the memory on my ARM linux system.
While reading back from the memory I'm getting a segfault after reading 64170 characters from the start address, ie at 0x3000FAAA.
If I change my starting address to 0x3000FA64, then also I get a segfault after 64170 characters.
How do I ensure data is accessed correctly if Data > 4kB (page size) ?
I'm unable to understand the exact problem, so I'm adding the snippet of my code below:
#define MAX_RANGE 327679
int fd;
FILE* fd_table=NULL;
unsigned long int count = 0 ;
void * mem;
void * aligned_vaddr;
unsigned long aligned_paddr;
uint32_t aligned_size;
unsigned long int addr_phys;
uint8_t *addr;
int g_size = 1;
unsigned long int g_paddr = 0x30000000; //Starting physical address
while((count<MAX_RANGE)){
g_paddr = addr_phys;
g_paddr &= ~(g_size - 1);
aligned_paddr = g_paddr & ~(4096 - 1);
aligned_size = g_paddr - aligned_paddr + (g_count * g_size);
aligned_size = (aligned_size + 4096 - 1) & ~(4096 - 1);
/* Align address to access size */
aligned_vaddr = mmap(NULL, aligned_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, aligned_paddr);
if (aligned_vaddr == NULL) {
printf("Error mapping address\n");
close(fd);
return 1;
}
mem = (void *)((uint32_t)aligned_vaddr + (g_paddr - aligned_paddr));
addr = mem;
fprintf(fd_table, "%02X\n",addr[0]);
addr_phys +=1; //Increment byte address
count++;
}
Note:
1. There is no error in the write process, I have verified by viewing the segfault address with memtool.
2. The address 0x30000000 onwards is not used by the system (I have ensured that in the u-boot).