Phew, I shouldn't have slept in so long - I could have given a little more rapid response.
Brendan wrote:Imagine if 3 processes start reading from a file. This doesn't cause any problems so you allow the reads. Then one of the processes tries to do a write. Do you allow this write? You don't know if other processes are still reading from the file or not (no "close()"), so if you refuse to allow the write you may be refusing for no reason at all, and if you do refuse to allow the write then how long do you refuse for (until nobody has read for some arbitrary length of time, until the other processes terminate, until hell freezes over)? If you allow the write, then what happens if the other processes haven't finished reading? Do they get inconsistent data (half old and half new)? Do you refuse the reads from the other processes after a write occurs (and if so, how long for)?
Even if you get rid of all file sharing, then it still doesn't work because you've got no idea when any process has finished using any file (and when it's safe to allow another process to access the file). Maybe if a process accesses a file you could refuse to allow any other processes from accessing the same file until the first process has terminated; but this would severely cripple the OS (in terms of file sharing "throughput"), and it would also create some extra "denial of service" problems (e.g. a process that reads one byte from as many files as it can then repeatedly does "sleep()", which prevent any process from accessing any of these files forever).
I would probably handle the first situation like this: all reads are allowed all the times (assuming appropriate permissions), and so are writes (writes include deletes), but if a process performs a write operation, it gains an implicit lock on the file (or possibly only the modified section of it). If another process tries to write, the driver queries the first writer on it's use of that file, and then decides whether to release the lock. Remember, each process does keep track of open and closed files, but neither the kernel nor the drivers do - *that* is my limitation. If the writer performs a write, and the modifications are being read by a reader, the reader does the read, but is informed of the possibly inconsistent data.
You could potentially do a DoS using the write locks by writing to a bunch of files and then purposefully not relinquishing the locks, but that could be done equally with open(), and if that process is killed the locks are effectively relinquished. But this would not happen by accident, because the libc (and/or libsys) is responsible for the file handler table, and would respond truthfully to any driver queries.
Combuster wrote:ABA Problem
Not being able to have exclusive access to resources
Having to pass the filename every syscall.
ABA Problem - fixed by driver-level synchronization notification
Exclusive write access is totally possible (see paragraph above)
Filenames are not passed every syscall - only inode numbers
Brendan wrote:
jal wrote:
Though I agree with Brendan and Combuster on traditional file systems, a database approach would circumvent these problems. Yes, it creates other problems, but those have been solved various ways in the various database designs.
I don't see how a database approach changes anything at all, unless you're talking of caching all writes and doing an atomic commit (if any data that was relied on remained unchanged). For databases this works because fields are typically small, but for a filesystem you may need to cache 50 GiB of "pending writes" in 2 GiB of RAM.
I think he meant that you could have a process that sits atop a file and does all I/O for other processes - but that would require open() and close() style things to work, and therefore defeats the original purpose.
ru2aqare wrote:Connectionless (UDP) network transfers work because 1) noone else has access to the data stream between your computer and the remote host (network sniffers and other issues notwithstanding) and 2) you can't seek on a network stream. Whereas files can be used by more than one entity at the same time (think log file producer and consumer processes) and you can seek to an arbitrary offset in files. You also need "open" and "close" operations on a network stream, they are just named differently ("connect" and "close" for example).
If there were no "open" operations in your system, how would you create a brand new empty file? How would you acquire a handle to an existing file (assuming whatever uses the files resides in user space, and you don't want to give it access to the kernel's data structures).
But connect() and close() are only relevant to the socket on your machine in terms of a UDP transfer, not the connection itself - UDP in no way makes a "connection" abstraction at any point. I was only giving that as sort of an analogy anyway to explain my use of the word "connectionless". So TCP : UDP :: open/close : connectionless I/O, but UDP != connectionless I/O.
I would also provide a pair of system calls that would equate to touch and rm, to create and remove files. You could easily use that facility to create something like a pipe - you use touch on a pipe driver, it gives you a inode but does not inform the VFS (so it doesn't appear anywhere, and has no name), and then you pass that inode to a child process. That way, it's actually *more* flexible - it decouples creation and implicit naming due to the fact that you don't need to open() the file again.
Solar wrote:Innovative does not necessarily equal effective... I'd like to see a cost / benefit comparison here.
Okay, here's what I see as a cost/benefit weighing:
Advantages:
- fewer system calls (more compact interface)
- no tracking of connections (less data and code)
- no fixed maximum number of "open" files
- no closing on exit/crash (*very* important for my design, which makes this hard otherwise)
- higher throughput on writers with many readers due to worse-is-better error handling
- gives same interface on C API level (could be made POSIX compliant)
Disadvantages:
- unorthodox in general (people will think you're insane, mostly)
- requires rapid, synchronous, preemptible IPC (which I have)
- write lock release requires some cooperation (if process is not dead)
- more complex error handling for readers (could be wrapped in libc)
- strange interface for assembly programmers
And although innovative definitely doesn't mean effective, this whole open()/close() concept has been around since the dawn of UNIX, and I've never seen it questioned. Leaving any basic design untouched for that long, regardless of how good it is, is not a good idea. Plus, I think I may be able to make the connectionless design work, and quite well too.