[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [MiNT] Shared libs without MMU resources
Hi,
Frank Naumann wrote:
You oversee a common fact in the informatics: you can't get rid of the
complexity, you can only move them. You have it either in the compiler, or
in the library/programs or in the operating system.
Very true. Everybody asking for shared libraries here should remember
that! ;-)
I think that many people still don't see what VM (virtual memory,
although "virtual addressing" would be a better term) or a MMU (memory
management unit) has to do with shared libraries and dynamic linking.
Why do you need that stuff?
Take this example (and you gurus out there, please forgive me the
simplification):
extern int errno;
FILE* fopen (const char* path, const char* mode);
A real word example. Say, this function gets called with an invalid
path argument. The function returns a NULL pointer and sets the global
variable errno to ENOENT. But what happens on assembler level?
In the static MiNT world, we call the function fopen() which means, we
jump to the address that contains the function code. The program
counter PC will crawl through the code, and finally get to some code,
where it has to write an integer (ENOENT) to the memory location that
the variable "errno" points to.
Now we start dreaming of shared libraries. What would have to happen
here? Multiple programs could shared the (assembler) code for "fopen",
no problem, the logic of the function is the same for every process, so
should be the code. True? No, unfortunately not: The function code has
to know the memory location for the variable "errno", and this location
must - of course - differ for every process.
You may say now, "global variables like errno are evil anyhow...". But
that global variable "errno" is only one example of many in the evil
real world. Other examples are stdin, stdout, stderr. Or look through
the MiNTlib headers for reentrant versions of library functions (all
those functions with names ending in "_r"); all of them would cause the
same problem that "errno" does.
So, what can we do? The problem is complex, and according to Frank, we
can only muse where to put that complexity but never get rid of it:
1) The standard approach is probably the most efficient: We completely
ignore the problem in user land. But in kernel land, we maintain an
address transformation matrix for each process, and the seemingly fixed
(virtual) address of "errno" is mapped by the MMU to a real address that
now differs for every process. In other words:
printf ("Address of errno: %p\n", &errno);
would yield the same address for each and every process running in the
system (using the shared libc).
Sounds inefficient, but it is not. The MMU is a very fast working device.
Drawback: Old MiNT hardware (mc68000) does not have a MMU but it does
have a strong lobby here. ;-) Furthermore, several Atari idiosyncrasies,
like the variable length block of system variables, cookie jars, closed
source tsr programs, ill-designed ipc protocols, etc. will make the
whole thing very hard to implement when you still want to be able to run
legacy software.
2) We modify our compiler system: We have to insert kind of an
indirection whenever the address of an unsharable memory location is
needed. One working implementation would be to write a process specific
address offset into one fixed processor register, make sure that no code
ever touches this register, and when we have to retrieve the address for
"errno" we always add that variable offset to the fixed address.
This approach basically works but also has drawbacks. The extra
indirection costs (little) performance, sure. But worse: Our standard
compiler suite gcc does not support such a feature. There used to be
the -mbaserel patch for Atari gcc, but it was an inofficial hack, which
was extremely hard to maintain.
Yet, somebody with a profund knowledge of gcc internals would probably
be able to code that feature into the compiler and convince the gcc
maintainers to accept the patch. The work that has then be done on the
assembler, linker and in the kernel, is probably easy compared to the
compiler modifications.
3) Eliminate the problem by writing code (and interfaces) that only
results in 100% sharable code. For example you could change the
interface of fopen() to:
FILE* fopen (const char* path, const char* mode, int* errno);
This is the SLB approach. Drawback: You have to re-write virtually
everything, because most real-world code is not written in such an
SLB-friendly way. What you want is a shared libc, a shared libjpeg or
libpng, and these libraries could not be implemented as an SLB.
So what could you do? Solution 3) is actually a hack, not a solution.
The SLB will never be a libtool target, and will only be useful for
proprietary Atari stuff.
Solution 1) is the cleanest, but the price is high. You will probably
have to say goodbye to a lot of legacy software. Multiply the problems
with memory protection by the number of TSRs in your auto folder to get
an idea of the complexity.
Solution 2) would be feasible, but requires a joint effort of people
with a deep insight into gcc, binutils and kernel.
You want to know my opinion? Try option number 2! Whereas the success
for 1) is uncertain (the real problems are hard to foresee), approach 2)
will certainly work, and you might even get some help from the embedded
fraction.
Ciao
Guido
--
Imperia AG, Development
Leyboldstr. 10 - D-50354 Hürth - http://www.imperia.net/