6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Wed May 08, 2024 2:04 pm

All times are UTC




Post new topic Reply to topic  [ 89 posts ]  Go to page Previous  1, 2, 3, 4, 5, 6
Author Message
PostPosted: Tue Mar 17, 2015 6:43 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
BigEd wrote:
Sounds good, but you'd need to be sure that such a small increase has actually changed the value... looks like it's a 14-digit capable system so that should be ok.

CDN under Thoroughbred BASIC reads out as xxxxxx.xxxxxx, which can be reliably incremented in 0.000001 units by setting the task's precision to 6. For the purposes of generating a filename, I convert the CDN value to a string, remove the decimal point and then convert it again to hex. Since each task under Throughbred has a unique ID, I then append the task ID to the filename, e.g., A4FB0103.014, producing what should be a system-unique value. The attempt is made to create the temporary file, and if a file exists error is raised, I repeat the process with the CDN value incremented.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Last edited by BigDumbDinosaur on Tue Mar 15, 2022 5:15 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 17, 2015 8:09 pm 
Offline

Joined: Sun Apr 10, 2011 8:29 am
Posts: 597
Location: Norway/Japan
There is a mechanism in the Linux kernel where an ever-incrementing time is returned as BigEd describes, but I can't remember exactly where, from the top of my head - it's been years since I followed every line of code added to the kernel[1]. But I remember that this was discussed and implemented.

[1] Kernel version 2.3.51 was the final one, after that I stopped reading all the patches :-)
-Tor


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 17, 2015 9:00 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
Tor wrote:
There is a mechanism in the Linux kernel where an ever-incrementing time is returned as BigEd describes, but I can't remember exactly where, from the top of my head - it's been years since I followed every line of code added to the kernel[1]. But I remember that this was discussed and implemented.

I believe you are referring to the clock_gettime() kernel API, in which it is possible to request the time value of a system clock that cannot be set and free-runs as long as the system stays up. The call is:

Code:
clock_gettime(clockid_t CLOCK_MONOTONIC struct timespec tp)

where the elements in tp would return an integer second count since (presumably) the last reboot and an integer that is the fractional component of the second count in nanoseconds. clock_gettime() is a POSIX standard API.

Quote:
[1] Kernel version 2.3.51 was the final one, after that I stopped reading all the patches :-)

I've never really followed it much. There are only so many hours in a day and I try to use them in a productive way. :D

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Last edited by BigDumbDinosaur on Tue Mar 15, 2022 5:17 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 03, 2022 7:54 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
This topic has languished for some time.  However, Chad Burrow’s recent topic concerning methods of mass storage, along with some recent programming activities of mine, prompted me to revive this topic.

Some pages back, I described the architecture of the “816NIX” filesystem I want to implement on my POC units’ mass storage.  Several things held me back.  One was machines prior to V1.3 only had bank $00 RAM, and realistically there was insufficient RAM to efficiently implement a kernel that included a filesystem driver, along with the necessary buffer space, whilst leaving adequate room for a user program.  With V1.3 offering another 64KB, these concerns have been alleviated, as extended RAM can be used for user-space, leaving all of bank $00 for a kernel, stack, buffers, etc.  This has spurred me to get back on this project.

Some recent code-pounding has focused on low-level functions that will be essential to being able to build and manage a filesystem.  One of them writes an initialized bitmap to the disk.  Another is a function that clears areas of the disk to a known state, usually nulls.  Past efforts used repetitive SCSI write transactions, along with some arithmetic to keep track of progress.  In wall-clock time, it was a slow process and not having a good means of speeding up SCSI read/write performance (no DMA controller :cry:), my enthusiasm was cooled by the prospect of processing that would be slower than a snake in a snowstorm.

Where things really bogged down was in initializing the filesystem’s inode structures, especially the inode array itself.  A maximum of 16,777,216 inodes may be configured per filesystem (one inode per file or directory).  Within the filesystem, disk space is allocated in 1KB “logical blocks,” which means the inode allocation bitmap (IAM) of a new filesystem would consist of 2048 contiguous blocks (8192 bits in a logical block) with all but one bit set to 1.  Assuming an average write speed of 750KB/second,¹ it would take about three seconds to generate the IAM for a maxxed-out filesystem.  Certainly tolerable, but...

Each inode has an on-disk size of 128 bytes, which means eight inodes can fit into one logical block.  In a maxxed-out filesystem, 2,097,152 contiguous logical blocks must be assigned to the inode array.  If an inode is not in use it must be initialized to nulls so a filesystem checker will know it’s not allocated to a file.  Hence part of generating a new filesystem will involve writing 2,097,152 logical blocks full of nulls.  Again assuming an average write speed of 750KB/second, it would take nearly 50 minutes to initialize the inode array.  In actuality, it took over an hour during testing I did on POC V1.1, whose Ø2 clock runs at 12.5 MHz, vs. 16 MHz with V1.3.  Whether 50 minutes or an hour, it was much too slow.

What changed things was some information in a Seagate SCSI technical paper that was apparently meant only for internal use, a copy having fortuitously come my way.  In this paper, mention is made of an obscure SCSI command called “write same.”  When issued this command, the disk will replicate a block a variable number of times, up to a maximum of 65,535 times, to be exact.  The nature of the command suggests it was intended for production testing of new units.  However, I have also determined that the SBC-3 SCSI standard marks this command as mandatory for direct-access devices, i.e., disks.

Anyhow, the procedure involved is like that of writing multiple blocks, which normally would be SCSI opcode $2A, paired with a 16-bit number of blocks, referred to as the transfer size (NBLK).  When initiated, the drive will keep requesting data during the data-out bus phase and the host will keep putting bytes on the bus until the drive has received enough.

The SCSI opcode for “write same” is opcode $41 and the NBLK parameter is the total number of identical blocks to be written.  The drive only requests one block of data from the host during the data-out bus phase and then after buffering the block in its own RAM, writes the block at the requested logical block address (LBA) and then replicates it NBLK -1 times, advancing the LBA by one block after each write.  The host machine can wait for the drive to finish or can go on to something else.

The block duplication is done entirely in the drive at whatever speed it can manage, and without host involvement.  Some testing I did with a 15,000 RPM Seagate drive suggests an effective duplication rate around 97,000 (512 byte) blocks per second, far beyond the capabilities of POC V1.3 using repetitive writes, even with writes involving large buffers.  I also tested with a slower 7200 RPM drive (an old Maxtor unit pulled out of a server years ago).  The duplication rate was about 35,000 blocks per second, which is still well beyond what V1.3 could do writing in 32 KB chunks.

With this new-found knowledge, I now had a way to generate a filesystem in a reasonable amount of time.  Testing with the 2,097,152 block count required to initialize the inode array of a maxxed-out filesystem showed that that step in filesystem generation would only require 43 seconds to complete.  It goes without saying that is a huge improvement over 3000 seconds (50 minutes).  :D  Incidentally, writing the IAM using the “write same” technique took about 0.045 seconds to complete.

Another relatively large structure that has to be initialized during filesystem generation is the data block allocation map (BAM).  As with the IAM, the BAM is an inverted bitmap, with each bit assigned to a data block.  The 816NIX filesystem can have a maximum data area size of 64GB, which given the 1KB logical block size, means the data area will have 67,108,864 data blocks in a maxxed-out filesystem.  Fortunately, the data blocks don’t have to be initialized to any particular state, so the approximate 12 minutes that would be required to do so can be avoided.²  However, the BAM has to be initialized, which in a maxxed-out filesystem, consists of 8192 logical blocks.  Using “write same,” I’d expect that generating the BAM would take around 0.09 seconds to complete, which would be an almost-imperceptible period of time.  I have not empirically determined that, but given what I have determined with other testing, expect it to be a realistic number.

All-in-all and using the above numbers, I’d expect it would take under a minute to generate a maxxed-out, 64GB filesystem, with a root directory containing . and .. entries.  Definitely tolerable!  :D

————————————————————
¹POC V1.3 can theoretically do 800KB/second, but there is bus protocol management overhead that is unavoidable.

²I might make clearing the data blocks to nulls an option in the filesystem generator, although I would think it would be a feature with limited value.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Last edited by BigDumbDinosaur on Wed Dec 06, 2023 8:38 am, edited 3 times in total.

Top
 Profile  
Reply with quote  
PostPosted: Wed Mar 16, 2022 4:58 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
BigDumbDinosaur wrote:
Another relatively large structure that has to be initialized during filesystem generation is the data block allocation map (BAM).  As with the IAM, the BAM is an inverted bitmap, with each bit assigned to a data block...

The function required to generate on-disk bitmaps has been written and tested.  It is a “universal” function, in that it works with with any-sized bitmap on any SCSI disk and at any logical block address (LBA).  The calling syntax for this function is:

Code:
         pea #imgptr >> 16     ;block image pointer MSW
         pea #imgptr & $ffff   ;block image pointer LSW
         pea #sizptr >> 16     ;physical block size pointer MSW
         pea #sizptr & $ffff   ;physical block size pointer LSW
         pea #nelmptr >> 16    ;number of elements pointer MSW
         pea #nelmptr & $ffff  ;number of elements pointer LSW
         pea #lbaptr >> 16     ;starting LBA pointer MSW
         pea #lbaptr & $ffff   ;starting LBA pointer LSW
         pea #idptr >> 16      ;device ID pointer MSW
         pea #idptr & $ffff    ;device ID pointer LSW
         jsr mkodbmap          ;make on-disk bitmap
         bcs error
;
         stx nblks             ;blocks used LSW
         sty nblks+2           ;blocks used MSW

The physical block size in bytes for the target device is retrieved and stored at the location pointed to by sizptr, which makes this function work with different-sized storage media.  The requirements of the target device, other than having enough capacity to accept the bitmap, is that it must be capable of random access and capable of repetitively writing an arbitrary number of identical blocks, up to 65,635 maximum.  All SCSI disks adhering to the SCSI-2 1994 or later standards have that capability.

The starting LBA is the origin of the bitmap on the disk.  If the bitmap is of sufficient size, it will span multiple blocks, which will be logically contiguous.  That is, if the starting LBA is 100, the next block will be 101, followed by 102, etc.  I use the term “logically contiguous” because the exact manner in which the disk organizes blocks doesn’t guarantee that consecutive LBAs point to physically-contiguous blocks—disk geometry is normally not known to applications, and doesn’t need to be known (it can be retrieved, however, if wanted).

The block image buffer is where pieces of the bitmap are built.  It has to be at least the size of a physical block (the value pointed to by SIZPTR), and in this implementation, can be anywhere in RAM without regard to bank boundaries.  The physical block size is retrieved using a SCSI library function that interrogates the disk, using the “read capacity” command.  That function also returns the number of blocks on the disk and the capacity in bytes.

Upon successful completion, mkodbmap will return the number of blocks used to build the bitmap in the .X (LSW) and .Y (MSW) registers, and the location pointed to by LBAPTR will be updated to the LBA immediately following the end of the bitmap, which is conceptually (LBAPTR) = (LBAPTR) + NBLKS.

Attached below is the source code and assembly listing for the mkodbmap bitmap generator function, which is 65C816 assembly language.  A feature of the code is all of the function’s workspace is defined on the stack—the opening lines of the source code illustrate how this is done.  When the function terminates it will clean up the stack to dispose of workspace and the call parameters.

Functions in my integer math library are called with pointers that are in the stack static workspace, the pointers pointing to variables generated during bitmap computations.  Other calls are made to the SCSI library for primitive disk access, again using pointers to data.  I use this basic method in all functions I write, which produces sufficient transparency to make each function look like a “black box” to the caller.

The way in which mkodbmap works is straightforward.  As previously explained, each bit in the bitmap tracks one “element” or “object,” which in my filesystem will be a block allocation map block (i.e., a bitmap tracking a bitmap), an inode or a data block.  The bitmap is “inverted,” which means a set bit corresponds to a free or unallocated object. 

In order to generate the bitmap, a series of disk blocks containing as many bits as there are objects to track has to be written.  A bitmap will have one “final” block, which will be the last block in the bitmap, along with one or more “intermediate” blocks if the number of active bits in the map is greater than the number of bits in a disk block.  All bits in an intermediate block are “significant,” whereas that won’t always be the case with a final block.  The arrangement and structure of blocks used to build the bitmap is the map’s “geometry.”

On a typical hard disk, a block contains 512 bytes or 4096 bits.  Therefore, one block can track up to 4096 objects.  Sticking with that number, if the number of objects to be tracked is equal to or less than 4096, only a final block is required in the bitmap.  If more than 4096 objects are to be tracked, one or more intermediate blocks will be required.  The total blocks required to build any given sized bitmap would be:

    NBLK = NOBJ / 4096 + ((NOBJ % 4096) != 0)

in which NOBJ is the number of objects to be tracked by the bitmap and NBLK is the number of 512 byte disk blocks required to build the map.  % represents the modulus operator.  Algebraic precedence applies, all operations are on integers and division works like the floor() function in C.

The number of intermediate blocks required would be:

    NINTM = NBLK - 1

The number of significant bits in the final block would be:

    FBBITS = NOBJ - NINTM * 4096

If the number of objects is an exact multiple of the number of bits in a block, then all blocks will be full, meaning all bits in each block will be significant.  If the number of objects being tracked is less than the number of bits in a block, the final block will have some “dead” bits, which will always be cleared.  Also possible is the number of objects being tracked is not an exact multiple of 8.  If that is the case, the final significant byte of the final block will have some “dead” bits, which too will always be cleared.

Once the bitmap geometry has been calculated, it is determined if intermediate blocks are needed.  As all bits in an intermediate block are significant, the 65C816’s MVN block-copy instruction is used to rapidly fill the block image buffer with %11111111.  Following that operation, the disk is commanded to duplicate the image for as many times as there are intermediate full blocks, using the “write same” command.  This procedure goes quickly, even with a large number of blocks to be duplicated.  Following the generation of the intermediate blocks, the final block is generated and written.  With that done, the bitmap is ready for use.

Attachment:
File comment: Bitmap Generator Source Code
mkodbmap.asm [20.84 KiB]
Downloaded 35 times
Attachment:
File comment: Bitmap Generator Assembly Listing
mkodbmap.txt [320.42 KiB]
Downloaded 40 times

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Last edited by BigDumbDinosaur on Wed Dec 06, 2023 8:58 am, edited 3 times in total.

Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 29, 2022 3:34 am 
Offline
User avatar

Joined: Tue Mar 05, 2013 4:31 am
Posts: 1373
Most interesting.... the find of the "write same" command. That prompted me to do a bit of research as well. Turns out that some earlier ATA spec showed command $E9, which was also called "write same" and apparently did the same thing. I think it was listed as obsolete around ATA-3, then removed after that. But, ATA-8 seems to have brought it back, but not as a standard command, but in the later SCT command transport. This does make me wonder if the command has also been kept or removed from a later SCSI specification. I still have a small collection of SCSI drives, but none are that large, being from the latter 80's and 90's.

_________________
Regards, KM
https://github.com/floobydust


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 29, 2022 5:37 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
floobydust wrote:
Most interesting.... the find of the "write same" command. That prompted me to do a bit of research as well. Turns out that some earlier ATA spec showed command $E9, which was also called "write same" and apparently did the same thing. I think it was listed as obsolete around ATA-3, then removed after that. But, ATA-8 seems to have brought it back, but not as a standard command, but in the later SCT command transport. This does make me wonder if the command has also been kept or removed from a later SCSI specification. I still have a small collection of SCSI drives, but none are that large, being from the latter 80’s and 90’s.

SBC-3 (SCSI block commands, revision 3), published in 2005, lists “write same” as mandatory for direct access devices (i.e., disks).  The command is also listed as mandatory in SBC-4, published in 2017.  The oldest SCSI document of any kind I have that lists “write same” as supported was published by Seagate in 1997.

Just for grins, I tested some old disks I have here that came out of servers we had upgraded.  I have seven of them and all support “write same,” including a Seagate Barracuda drive that was manufactured in May 1996 (it’s the only “narrow” bus unit I have—not sure why I hung on to it).  I was a little surprised with the Barracuda, since its architecture is SCSI-2 1994.  That being the case, I suspect “write same” was in the SCSI-2 1994 draft standard but likely was listed as optional at the time.  Seagate was usually the first to implement new SCSI features, so it being supported in a 26-year-old disk maybe isn’t as startling as one might expect.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Last edited by BigDumbDinosaur on Wed Dec 06, 2023 9:11 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 13, 2022 4:56 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
Thanks to some discussion over here, I’ve got a mathematical method to move forward with the filesystem.  Here’s the general flow...

To build the filesystem, the user enters the filesystem name, filesystem size in logical blocks, and number of inodes.  After entering the filesystem size, the following equation is solved:

Code:
INODES = ((FSS - (FOH + MINDA)) × 1024 ÷ 1025) × 8

...where FSS is the filesystem size entered by the user, FOH is immutable overhead, MINDA is the minimum number of blocks that must be reserved for the data area, 1024 is the size of a logical block and 8 is the number of inodes that will fit in a block.  INODES is capped at 16,777,216 due to the way in which I’ve implemented the inode allocation bitmap (IAM).

Once INODES is known:

Code:
INODE_BLKS = INODES ÷ 8

...and:

Code:
IAM_BLKS = (INODES + 8191) ÷ 8192

...where INODE_BLKS is the number of blocks needed to store the inode array,  IAM_BLKS is the number of blocks needed for the IAM and 8192 is the number of bits in a block.

Having computed the space needed by the inode array and the IAM, whatever is left over is available for the data area:

Code:
DATA_BLOCKS = (FSS - (FOH + INODE_BLKS + IAM_BLKS)) × 1024 ÷ 1025

With the above known, the required space for the data block allocation bitmap (BAM) can be determined:

Code:
BAM_BLKS = (DATA_BLOCKS + 8191) ÷ 8192

The filesystem geometry the above implements will support up to 64GB of data storage and a maximum of 16,777,215 files¹, minus directory overhead, i.e., the . and .. entries.  Any one file can be up to 4 GB minus a byte in size, a limitation of the 32-bit arithmetic used in all filesystem calculations.

A maximized filesystem will be divvied as follows:

Code:
         3  overhead blocks — FOH
 2,097,152  inode blocks    — INODE_BLKS
     2,048  IAM blocks      — IAM_BLKS
67,108,864  data blocks     — DATA_BLOCKS
     8,192  BAM blocks      — BAM_BLKS
—————————————————————————————————————————
69,216,259  filesystem size — FSS

My current mass storage layout will accommodate a maximum of six filesystems per disk, which is enough to use up all space on a 300 GB disk.

Next step will be to write the code that will make it work.

—————————————————————————————————————————
¹Inode $00 is not usable, as a directory entry’s inode number cannot be zero—$00 marks a directory slot as unused.  Hence the usable number of inodes will always be one less than that configured when the filesystem is built.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Last edited by BigDumbDinosaur on Wed Dec 06, 2023 9:13 am, edited 2 times in total.

Top
 Profile  
Reply with quote  
PostPosted: Fri Apr 15, 2022 1:18 pm 
Offline
User avatar

Joined: Tue Aug 11, 2020 3:45 am
Posts: 311
Location: A magnetic field
BigDumbDinosaur on Wed 16 Mar 2022 wrote:
all of the function's workspace is defined on the stack ... Functions in my integer math library are called with pointers that are in the stack static workspace


If all of your functions work on stack, you'd have a highly attractive system for C and Forth programmers. It might be slower, especially if the carry flag is hidden. However, it may add considerable appeal. 65816 with SCSI and a Unix filing system is highly desirable. 65816, SCSI and a Unix filing system programmed from C, Forth, BASIC or assembly is an irresistible combination.

Either way, it is a pleasure to see someone make a proper attempt at a filing system and storage because FAT32 on MicroSD is grossly unreliable.

_________________
Modules | Processors | Boards | Boxes | Beep, Beep! I'm a sheep!


Top
 Profile  
Reply with quote  
PostPosted: Fri Apr 15, 2022 5:21 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
BigDumbDinosaur wrote:
Thanks to some discussion over here, I’ve got a mathematical method to move forward with the filesystem.  Here’s the general flow...
Code:
INODES = ((FSS - (FOH + MINDA)) × 1024 ÷ 1025) × 8
...

As assembly language doesn’t have the concept of algebraic precedence, doing the math needed to solve the above equation requires that the program complete steps in a particular order.  The mumbo-jumbo required to do this is prone to entry errors in the source code due to the need to correctly order parameters on the stack before calling functions.  Naturally, this sort of thing is tailor-made for using macros.  With macros, a somewhat-understandable sequence of operations results.

First, here’s a taste of what is required to copy an integer to an accumulator as a prelude to performing an arithmetic operation:

Code:
;intntoa: COPY INTEGER TO ACCUMULATOR #1
;
;   Invocation example: PEA #N >> 16           ;source pointer MSW
;                       PEA #N & $FFFF         ;source pointer LSW
;                       PEA #INT_SIZE          ;integer size
;                       .IF .DEF(_IMATH_)
;                       JSL intntoa
;                       .ELSE
;                       JSR intntoa
;                       .ENDIF

It’s all-to-easy to reverse the order of the PEA #N >> 16 and PEA #N & $FFFF statements in the source code, causing what amounts to a “wild pointer,” to borrow a phrase from C, due to the endianess of the source pointer being reversed.  Even worse would be to mix up the order of the pointer and integer size on the stack.

The same thing done with a macro is a little easier to understand:

Code:
          intntoa usrcap,s_dword,’n’    ;usable capacity —> FACA

In the above, the S_DWORD parameter defines the size of the variable USRCAP—a DWORD being 32 bits—and the ’n’ parameter tells the macro to use “near” addressing, which means USRCAP is stored in the same bank in which the program is running.  Only the least-significant word (LSW) of USRCAP’s address would be used, with the most-significant word (MSW) being derived from the PB register.  If USRCAP were in a different bank, ’f’ would be used to tell the macro to use the MSW to generate USRCAP’s pointer, alone with the LSW.  There is also ’d’ addressing, in which USRCAP is a location on direct page that contains the address of the USRCAP variable.

The code required to do this is not pretty:

Code:
intntoa  .macro .s,.m,.a          ;copy integer to FACA
         .if .a == ’d’ || .a == ’D’
             pei .s+2
             pei .s
         .else
             .if .a == ’f’ || .a == ’F’
             pea #.s >> 16
             pea #.s & $ffff
             .else
                 .if .a == ’n’ || .a == ’N’
                     .rept s_word
                         phk
                     .endr
                     per .s & $ffff
                 .else
                     .if .a == ’r’ || .a == ’R’
                         rep #%00010000
                         phy
                         phx
                     .else
                         .error ""+@0$+": mode must be ’d’, ’f’, ’n’ or ’r’"
                     .endif
                 .endif
             .endif
         .endif
         pea #.m
         .if .def(_IMATH_)
             jsl intntoa
         .else
             jsr intntoa
         .endif
         .endm

Armed with a bunch of macros, I can solve the INODES = ((FSS - (FOH + MINDA)) × 1024 ÷ 1025) × 8 equation with some vaguely understandable code that is faster and a lot less error-prone to enter.  Here’s an excerpt from the filesystem generator program that’s in development:

Code:
;—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—
;COMPUTE MAXIMUM INODES FOR FILESYSTEM
;—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
;MAXINODES = (IFSSIZ — (FSOH + UMIN)) × LDBLKSZ ÷ (LDBLKSZ + 1) × INODLDB
;
;...where: IFSSIZ   = filesystem size        (blocks)
;          FSOH     = immutable overhead     (blocks)
;          UMIN     = data area minimum size (blocks)
;          LDBLKSZ  = logical block size     (bytes)
;          INODLDB  = inodes per logical block
;—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-
;
        
         intntoa ifssiz,s_dword,’n’    ;IFSSIZ —> FACA
         intntob fsohumin,s_word,’n’   ;FSOH+UMIN —> FACB
         intsub                        ;IFSSIZ—(FSOH+UMIN)
         intntob ldblksz,s_word,’n’    ;LDBLKSZ —> FACB
         intmul                        ;(IFSSIZ—(FSOH+UMIN))×LDBLKSZ
         intntob ldblksz1,s_word,’n’   ;LDBLKSZ+1 —> FACB
         intdiv                        ;(IFSSIZ—(FSOH+UMIN))×LDBLKSZ÷(LDBLKSZ+1)
         intntob inodldb,s_word,’n’    ;INODLDB —> FACA
         intmul                        ;MAXINODES

It doesn’t look like algebra at all, but it also doesn’t look too much like raw assembly language.

By the way, the above does work.  :D

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Last edited by BigDumbDinosaur on Wed Dec 06, 2023 9:22 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Fri Apr 15, 2022 5:37 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
Sheep64 wrote:
BigDumbDinosaur on Wed 16 Mar 2022 wrote:
all of the function’s workspace is defined on the stack ... Functions in my integer math library are called with pointers that are in the stack static workspace

If all of your functions work on stack, you’d have a highly attractive system for C and Forth programmers.

Probably, although I have little interest in C and no interest in Forth.  I wrote the library to facilitate integer arithmetic in assembly language, specifically with the 65C816 in mind.  The library would be difficult to apply to the 65C02, since the latter lacks the stack addressing and direct page relocation capabilities of the 816 (plus the arithmetic functions are all written with 16-bit code).  Also, the stack model I use isn’t the same as that of a typical C compiler.  The model I developed, again, is meant to optimize its use in assembly language.

Quote:
Either way, it is a pleasure to see someone make a proper attempt at a filing system and storage because FAT32 on MicroSD is grossly unreliable.

The FAT filesystem model is what made Peter Norton wealthy.  :shock:

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Last edited by BigDumbDinosaur on Wed Dec 06, 2023 9:24 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Sat Apr 16, 2022 8:49 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
BigDumbDinosaur wrote:
Thanks to some discussion over here, I’ve got a mathematical method to move forward with the filesystem.  Here’s the general flow...

Unfortunately, the above method doesn’t work right.  The computations that work out the number of inodes, the blocks needed to hold the inodes and the blocks needed for the inode allocation bitmap produce the expected results for different-sized filesystems.  Where it breaks down is at:

Code:
DATA_BLKS = (FSS - (FOH + INODE_BLKS + IAM_BLKS)) × 1024 ÷ 1025

With that producing erroneous answers, this also goes belly-up:

Code:
BAM_BLKS = (DATA_BLOCKS + 8191) ÷ 8192

If I artificially set DATA_BLKS to the correct value for the filesystem size, BAM_BLKS is correctly computed.

I’ve determined the expression:

Code:
FSS - (FOH + INODE_BLKS + IAM_BLKS)

which is the number of blocks available for the data blocks and the blocks needed for the data block allocation map (DATA_BLKS and BAM_BLKS, respectively) is producing correct results.  So my DATA_BLOCKS equation is faulty.

I’ve been banging away at this for the last several days and can’t see where things went sideways.  I’d appreciate it if someone could take a look at it and perhaps spot the error.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Last edited by BigDumbDinosaur on Wed Dec 06, 2023 9:25 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Sat Apr 16, 2022 2:42 pm 
Offline

Joined: Fri Jul 09, 2021 10:12 pm
Posts: 741
It looks like the bit we talked about in the other thread was the inode calculation, where you fit 8 inodes into each block. But it looks like you're using almost the same calculation for data blocks, but not fitting 8 into each block. I think that changes the rest of the calculation a bit too.

So assuming you still need to split the remaining free space into data blocks and an allocation bitmap with one bit per block, this time one BAM block supports 8192 data blocks, so your fully-allocated multiblock chunk is going to be 8193 blocks in total supporting 8192 data blocks. Maybe try using these numbers instead of 1025 and 1024, for this part of the calculation.

It also feels like at present you are always going to have about MINDA blocks for data storage regardless of the filesystem size. That seems odd to me, it means all filesystems will have the same data area size, just different numbers of inodes.


Top
 Profile  
Reply with quote  
PostPosted: Sat Apr 16, 2022 9:14 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8176
Location: Midwestern USA
gfoot wrote:
It looks like the bit we talked about in the other thread was the inode calculation, where you fit 8 inodes into each block. But it looks like you’re using almost the same calculation for data blocks, but not fitting 8 into each block. I think that changes the rest of the calculation a bit too.

So assuming you still need to split the remaining free space into data blocks and an allocation bitmap with one bit per block, this time one BAM block supports 8192 data blocks, so your fully-allocated multiblock chunk is going to be 8193 blocks in total supporting 8192 data blocks. Maybe try using these numbers instead of 1025 and 1024, for this part of the calculation.

Duh!  :oops:  I got so fixated on blocks I wasn’t seeing the obvious error.  Changing × 1024 ÷ 1025 to × 8192 ÷ 8193 was all that was needed.  Now I am getting correct results.  :oops:  :shock:  :)  :D

Quote:
It also feels like at present you are always going to have about MINDA blocks for data storage regardless of the filesystem size. That seems odd to me, it means all filesystems will have the same data area size, just different numbers of inodes.

MINDA, which is the minimum size of the data area (combined data blocks and data block allocation bitmap blocks, or BAM), is a value that is only used to establish the minimum possible filesystem size.  There is a corresponding value for inodes, MININOD, values for MINDA and MININOD being 8192 and 32, respectively.  These values, along with the fixed overhead in the filesystem, result in a minimum filesystem size of 8201 blocks (which isn’t much of a filesystem).  The minimums are data entry checking values only—they do not affect filesystem geometry computations.

There are similar maximums, MAXDBLK and MAXINOD, which are 67,108,864 and 16,777,216, respectively.  MAXDBLK sets the limit for the number of data blocks in the filesystem—MAXDBLK is 64GiB of data storage.  These limits have to do with how the respective allocation maps are structured.

Knowing the minimums and maximums as described above, filesystem limits would be:

Code:
 Min FS Size: 8201
————————————————————————
  Max Inodes: 40
Inode Blocks: 4
  IAM Blocks: 1
   Data Area: 8193
 Data Blocks: 8192
  BAM Blocks: 1

In the above, the number of inode blocks corresponds to the inode minimum of 32, not the maximum.

Continuing:

Code:
 Max FS Size: 69,216,259
————————————————————————
  Max Inodes: 16,777,216
Inode Blocks: 2,097,152
  IAM Blocks: 2048
   Data Area: 67,117,056
 Data Blocks: 67,108,864
  BAM Blocks: 8192

The inodes value set during filesystem configuration is critical, since it establishes the maximum number of files and directories that can be created in the filesystem.  Going too small in the case of a large filesystem may result in a “filesystem full” error if many small files are created, despite there being plenty of data blocks available.  Going too big in a small filesystem may also result in a “filesystem full” error if a few large files are created, because too much of the total filesystem space is being consumed by inodes, not leaving enough space for data blocks.

Experience has shown that a relationship of 32:1 between filesystem blocks and number of inodes works well for most applications.  That being the case, the recommended number of inodes for a maximum-sized 816NIX filesystem would be 2,163,008.  Since MAXDBLK is capped at 64GiB for structural reasons, there will be 2,099,195 unused blocks in the filesystem with that inode value.  In the case of a maximum-size filesystem, the maximum possible number of inodes might as well be configured, since the corresponding inode blocks will otherwise be unused.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 89 posts ]  Go to page Previous  1, 2, 3, 4, 5, 6

All times are UTC


Who is online

Users browsing this forum: Google [Bot] and 11 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: