The C FAQ

W

William Hughes

Richard Tobin a crit :
Why Unix didn't get used in the early microprocessors?


"The early microprocessors" is a very wide field. Indeed, Unix
was used on many, several were designed for Unix. A more
interesting question is why Unix did not get used in the
early intel processors. The answer is that it was to
some extent (eg Xenix). These attempts never made much
headway, because the intel stuff was low powered. Forward
to the early 90's. If you wanted to do "serious" computing
you needed a "workstation" class processor. These mostly ran
some version of Unix, so there was a lot of unix development.
On the intel side, there were only relatively low powered
machines, and there was no good unix system. So you
were faced with a difficult porting job that would probably
not work very well.

Three things happened:

- The intel hardware got a lot more powerful,
so you could get workstation performance at
an order of magnitude lower cost.

- NT

- Linux

Given the hardware costs moving to intel was inevitable.
The NT POSIX layer did not work so porting to NT was hard.
The niche that had been taken by the SUN, SGI, etc.
workstations went to intel/Linux. My contention is that
if the NT POSIX layer had worked correctly from the beginning
Linux would be very much marginalized.

- William Hughes
 
A

Andrew Poelstra

Given that, even now, there are current production systems - not very
many, I grant you, but still *some - that rely on MS-DOS and which are
still being maintained and developed, two of the other three adjectives
are open to question, too. As for "old", the MS-DOS-specific material is
almost certainly about as old as much of the other material. Steve
Summit did do some C99-related updates a few years ago, but not many.

I would suspect, though, that the people tasked with
maintaining those DOS machines are not brand spanking
new C programmers who need (or want) to read the FAQ.

Jacob's point is that the "real" brand spanking new C
programmers will see the DOS questions, remember from
their Java courses that all Old things are Bad, then
end up posting here anyway (or worse, doing their own
thing).


Andrew
 
A

Andrew Poelstra

In one sense, your reply is perfectly justified.

Well, I realized after the fact that if I replaced "though"
with "however" and "Bad" with "Evil", I could have been rid
of those pesky spaces.

But I should add that as near as I can tell, there aren't a
whole lot of people coming here with silly questions caused
by mistrust of the FAQ. Perhaps merely being Usenet in 2010
is enough to be inaccessible to most newbies?


Andrew
 
S

Simon Connah

Yes.

Also, so far as I can tell, the death of "UNIX vendors" has a great deal to
do with the widespread availability of high-quality free competition. Which
is ultimately a great thing -- commoditize the bits we know how to do so
people can focus their budgets and effort on something unique or specific.

-s

Actually as far as I can tell the major cause of the death of UNIX
vendors was a fragmentation of the market. At one point there was a
very large difference between System V and BSD flavours that didn't
help the UNIX cause one bit. It took Linux emerging for the big UNIX
vendors to get together and actually create worthwhile crossplatform
standards.
 
J

jacob navia

Nick Keighley a écrit :
because it wouldn't fit.

Apparently you have never heard about the PDP 11 then.
A 286 board was MUCH more powerful than the PDP 11,
and Xenix proved that Unix was feasible in a 386 board.

I used the Fortune machine with a 68000 processor that supported
9 users with 2MB of RAM.

I participated into the porting of Unix into the first 386
board that came in France, almost right after it came out
around 89-90 for Goupil, a French PC maker of that time.

Unix run circles around MSDOS... but the project was stopped
because management decided that MSDOS was better.

Steve Jobs proved (around that time) that a user friendly
UNIX was feasible. Problem was, I could never afford that
hardware, the mass market wasn't their customer target.

Microsoft targeted the mass market. And won, against all
odds because of the attitude of Unix vendors.
 
J

jacob navia

Simon Connah a écrit :
Actually as far as I can tell the major cause of the death of UNIX
vendors was a fragmentation of the market. At one point there was a very
large difference between System V and BSD flavours that didn't help the
UNIX cause one bit. It took Linux emerging for the big UNIX vendors to
get together and actually create worthwhile crossplatform standards.

what?

Linux is even more fragmented that the different Unix flavors at that time.
 
S

Seebs

Microsoft targeted the mass market. And won, against all
odds because of the attitude of Unix vendors.

No, against no odds at all. Microsoft won because of the IBM PC, and
network effects.

BTW, to answer your question: I made some comment about industry shifts,
and found out that there are still live systems in production -- and NEW
systems being PUT INTO production -- running DOS.

A friend of mine does fire alarm control systems. They have very small
CPU requirements, EXTREMELY high predictability and testing requirements,
and some such software has been extremely thoroughly vetted for DOS or
Windows 98 environments. As a result, there are people building new buildings
who are setting up new machines. Running DOS. Because they are SURE it
will work.

So, no, DOS is not yet dead. Much though we might wish it to be otherwise.

-s
 
S

Seebs

Linux is even more fragmented that the different Unix flavors at that time.

I wrote a hunk of code which has one meaningful #ifdef in it, which is
working on every Linux system we know of currently available. It's not
quite stable enough that you can use binaries from one system on another,
but that's because it is by design an EXTREMELY non-portable piece of code.
I don't mean "currently in production" -- I mean we're including Red Hat
Enterprise Linux 4, which dates back to 2005. I've had to check for the
availability of precisely one feature that didn't exist in RHEL4, and
everything else I need is adequately consistent between RHEL4 and Fedora 12,
OpenSuSE 11, and so on.

That's substantially better compatibility than we used to have between BSD
and SysV.

For that matter, this is one of the only pieces of code I've seen in years
that wouldn't run perfectly well on any BSD or Linux or Solaris type system.
The migration towards consistent interfaces occurred mostly in the 90s, and
is now adequately complete.

.... Okay, I gotta bring this back on topic.

The "#ifdef" remark above refers to a convention which has extremely high
expressive power for dealing with portability. The idea is that in addition
to providing declarations of functions and types, headers can also provide
what are called "feature test macros" -- macros which are defined if and only
if a particular feature is available. This allows code which would like
to support a given feature to do so where that feature is available, while
falling back on an alternative where it isn't.

Another variant of this is feature-request macros, where a particular
macro, if defined before a header is included, has magical effects
on the header. For instance, if you're compiling things on a typical
Linux box, there are a lot of extra features which will be disabled if
you've put the compiler in a reasonably standard mode. You can bring many
of them back by doing something like:
#define _POSIX_SOURCE
or
#define _GNU_SOURCE
before including various headers.

Well-documented and standardized things like this make it easier to write
code which is portable across a variety of implementations, while getting
additional performance on others. Even if you can't work without a feature,
it can be preferable to use a #error directive to diagnose the specific
problem rather than leaving the reader with a huge stream of "syntax error
before..." messages.

-s
 
A

Alexander Bartolich

jacob said:
[...]
HP and IBM produced PCs in great numbers, I just do not
understand why you do not see such a difference!

The best-selling single personal computer model of all time is the
Commodore 64. Yet Commodore is no more. Same can be said of Atari,
Sinclair, the BBC Micro, the MSX standard, etc. To my knowledge out
of all companies that targeted the mass market with 8-bit and 32-bit
home computers only one survived: Apple.

--
 
J

jacob navia

Seebs a écrit :
I wrote a hunk of code which has one meaningful #ifdef in it, which is
working on every Linux system we know of currently available.

First bet:

It doesn't use any GUI

Second bet:

It doesn't use any sound/video/multimedia

Third bet:

It doesn't use any graphics, or mouse

Obviously, THEN... it is portable. But it would be portable to
Mac/windows/whatever too without much pain.
 
J

jacob navia

Richard Heathfield a écrit :
Actually, there's no reason why it shouldn't, given that he is only
claiming portability across "every Linux system we know of currently
available". The same applies to your other two bets...

I can use the same code from windows 95 to windows me to windows NT/XP
to VISTA and windows 7, using windows, mouse, graphics, sound, and
many other things with minimal problems. The same isn't true in
linux. You have the KDE/Gnome problem (QT/GDK) and within GDK, the
only that I used, the problem that each version is (of course) incompatible
with the last version. I gave up following GDK and rewriting my
application again

Why this mess?

Because windows is done by microsoft, a monopoly that wants to keep
most customers (and developers) happy to go on selling them stuff.

Linux is free, and the developers of linux GUIs do not care about users
since their user base doesn't pay them at all. So they do as they
find fit, and the users must rewrite all their applications at their
whim.

Obviously there are IMPORTANT part of linux that are rock solid since
there are CUSTOMERS that PAY linux developers to maintain those
parts: network, basic multi tasking, etc. Linux as a SERVER runs pretty
well since IBM/ORACLE/whatever PAY for top notch linux developers.

GUIs are used by "normal" users, and nobody gets payed for them.

Consequence: a mess.

In the linux bazaar, money is the center of attention like in any
other bazzar, even those that look like cathedrals.

The same situation appears in the linux developer tools, that are
at the same level that Unix 20 years ago...

vi+make+gdb

No sane linux developer would PAY for a development system. And
the consequence is that there are hundreds of unfinished "DUE"s:
"Desintegrated Unusable Environments" that promise the world
but deliver just crashes. The only one that halfway works is
Eclipse because IBM PAYED for it.
 
R

Rob Kendrick

I can use the same code from windows 95 to windows me to windows NT/XP
to VISTA and windows 7, using windows, mouse, graphics, sound, and
many other things with minimal problems. The same isn't true in
linux. You have the KDE/Gnome problem (QT/GDK) and within GDK, the
only that I used, the problem that each version is (of course)
incompatible with the last version. I gave up following GDK and
rewriting my application again

You speak as if Qt and GTK+ are the only options. But of course, you
know better than that.

(You can still run truly ancient Motif-based applications, or
applications that implement their own GUI via Xlib quite successfully.)

B.
 
S

Seebs

Seebs a écrit :
First bet:
It doesn't use any GUI

Right you are!
Second bet:
It doesn't use any sound/video/multimedia

Right you are!
Third bet:
It doesn't use any graphics, or mouse

Three for three!
Obviously, THEN... it is portable. But it would be portable to
Mac/windows/whatever too without much pain.

And here you've got a Nilges-grade error.

Lemme tell you what this hunk of code is. This'll drift a bit off topic,
but it's a very good example of how very non-portable a piece of code can
be. I would guess that I could get it running on OS X in a month or so,
but I am pretty sure it is semantically incoherent to talk about "porting"
it to Windows.

Background:
* Nearly all executables on Linux systems are dynamically linked, meaning
that they pick up the C library at runtime.
* Unix-like systems distinguish between "library" calls (made to code in
the C library) and "system" calls (made to the kernel).
* But actually, nearly all syscalls exist as tiny little stubs that are
found in the library and which contain the raw inline code to make a
syscall.
* The dynamic linker on Linux checks certain environment variables when
starting up, BEFORE it loads anything (including even the C library).
* These can cause it to do things like check paths other than the usual
system-wide linker path, and/or to load libraries BEFORE the shared
libraries a program was linked with, and which it was not linked with and
it has no knowledge of.
* The product I work on involves creating filesystems for embedded Linux
systems.
* Creating filesystems nearly always involves operations that require
root (sysadmin) privileges.
* It is extremely desireable that users not need root access on their
workstations to do it, though.

So.

The "hunk of code" alluded to above is a two-part thing. One part is a shared
library. Another part is a server. The shared library is designed such that,
if you put its name in the LD_PRELOAD environment variable, and a path to it
in the LD_LIBRARY_PATH environment variable, it will get picked up by any
dynamically-linked executable, and lookups of calls in it will pick up the
versions in this library BEFORE they pick up versions in the C library.

And it provides functions with the same names as a few dozen system calls,
and a couple of C-level library calls.

When you first hit one of these functions, it builds itself a table of the
"next" versions of all these calls (usually the ones in the C library, but
there could be other similar libraries; we don't care). And then it sets
up wrappers so that when you call the syscall open(), what you actually get
is my implementation of open(), which does some interesting magic stuff.

What this ends up doing is allowing me to intercept every system call that
deals with the filesystem and is affected by privileges, and act as though
you had root privileges, or at least, make it look as though it acted as
though you had root privileges. The client code does this by finding out
whether it has a server, and if not, starting a server, to which it then
sends messages informing the server about operations, or querying the server
about filesystem objects; the server maintains a database of files and
what to claim they are. So if you try to create a "device node" (a special
kind of Unix "file" which instead of having contents provides access to
the userspace interface of a device driver; e.g., a file named "sda1" might
end up hooked up to the first partition of the first SCSI disk on the
machine), what actually happens is:
1. I verify that there's not a file there, and if there is, fail
the same way the real syscall would have.
2. I create a plain file with no contents of that name.
3. If that succeeded, I send a note to the server telling it that
the file I just created should LOOK like it's a device node
with the given attributes.

Later, if you try to look at that file with, say, "ls -l", what actually
happens is:
1. I check the real file on disk.
2. I query the server about that path and file dev/inode (magic ID
uniquely identifying a file).
3. If I got anything back from the server, I replace the file's
"real" attributes with the attributes the server gave me.
4. I report back as though nothing happened.

There's a lot more, and some of it gets extremely arcane; there's magic
to intercept dozens of calls and do very surprising stuff.

The net result is that you can run an entire system install process and
end up with something which, viewed through this tool, looks just like a
properly-installed filesystem, complete with files owned by many different
users, device nodes, and everything. And then you can use other tools
which do things like create raw filesystem images or archives, and run
them in this magic mode, and get raw filesystem images which, dumped to disk
or flash, actually are working filesystem images with all the right modes.

The concept of porting this to Windows is frankly laughable. I could probably
get it working on BSD or OS X in about a month, give or take, but I'm not
totally sure; there's some pretty crazy assumptions in there, although I've
developed some neat trickery to allow this code to work even if some of the
"extra" calls it has don't exist on a given target. (e.g., some systems have
a family of calls with names like "openat()" or "unlinkat()" which allow you
to operate relative to a directory without having to specify the path to
that directory, others don't; my program wraps them if they exist, but works
fine even if they don't.)

Porting this between BSD and SysV in 1989 would have been much harder than
simply rewriting nearly all of it from scratch completely differently for
each, if it had even been possible to do it at all. (By 1990, you could
at least get ELF and dynamic linking on SVR4, so it might have been
possible there.) So, no, the Linux environment is NOT more fragmented than
the historical Unix market. They have greater code portability than anything
we ever saw back then.

-s
 
S

Simon Connah

So you basically admit its unportable "junk"?

You do realise that dont you?

Unportable != junk.

Some things just have to be tied to a specific operating system. There
is only so much abstraction one can do before one has to get into the
nitty gritty of implementation specific software.
 
E

Ersek, Laszlo

Lemme tell you what this hunk of code is.

This was very interesting, thanks.

When you first hit one of these functions, it builds itself a table of the
"next" versions of all these calls (usually the ones in the C library, but
there could be other similar libraries; we don't care).

I guess you use some kind of static variable (umm, an object of
integer type with static storage duration, internal linkage, file
scope) for checking if the preloaded lib was already initialized. Is
this thread-safe and/or async-signal safe?

Have you considered the "constructor" gcc function attribute?

http://gcc.gnu.org/onlinedocs/gcc-4.4.3/gcc/Function-Attributes.html
http://tldp.org/HOWTO/Program-Library-HOWTO/miscellaneous.html#INIT-AND-CLEANUP
http://tldp.org/HOWTO/Program-Library-HOWTO/miscellaneous.html#INIT-AND-FINI-OBSOLETE

As a lame "trick" question, how does the expression look where you
assign the value returned by dlsym(RTLD_NEXT, "open") to your
"orig_open" function pointer? :)

What this ends up doing is allowing me to intercept every system call that
deals with the filesystem and is affected by privileges, and act as though
you had root privileges, or at least, make it look as though it acted as
though you had root privileges. The client code does this by finding out
whether it has a server, and if not, starting a server,

What were the arguments against implementing this with FUSE?

Also, how does the client start a server? Does it fork()? Or by
posix_spawn()? Or by messaging a super-server?

Thanks,
lacos
 
S

Seebs

Unportable != junk.

If you are expecting to communicate with one of our standard trolls,
you have missed the point greatly.
Some things just have to be tied to a specific operating system. There
is only so much abstraction one can do before one has to get into the
nitty gritty of implementation specific software.

Exactly. Boot loaders are only marginally portable, but a good boot loader
is EXTREMELY valuable. (This lesson is driven home forcefully once you
have used some bad boot loaders.)

I would guess that I could extend this work to cover other modern Unix-like
systems with a reasonadble amount of effort; I've gone to great lengths to
make large hunks of the code reliable, clean, and portable. But
fundamentally, the task under discussion is inherently constrained to
particular environments.

-s
 
S

Seebs

I guess you use some kind of static variable (umm, an object of
integer type with static storage duration, internal linkage, file
scope) for checking if the preloaded lib was already initialized. Is
this thread-safe and/or async-signal safe?

Sort of. Actually, in the versions currently under use, it's not -- there
were no threaded programs we cared about, and the original version simply
ignored threading. The current version is nearly-thread-safe, as long as
one call completes before the various threads start. I could fix that,
but it hasn't been an issue.
Have you considered the "constructor" gcc function attribute?
No.

As a lame "trick" question, how does the expression look where you
assign the value returned by dlsym(RTLD_NEXT, "open") to your
"orig_open" function pointer? :)

I don't. :)

What I have is:

1. I define a function named open() with the same signature as the syscall.
2. I define a function named dummy_open() with that same signature, too.
3. I define a pointer-to-function named real_open() with the same signature.
4. I define a function named wrap_open() with the same signature.

My open() looks roughly like
if (setup_wrappers()) {
if (get_lock()) {
if (magic_mode)
wrap_open(args);
else
real_open(args);
drop_lock()
} else {
fail
}
} else {
dummy_open();
}

The "magic mode" thing is so that the library internals can bypass all
this magic, and is one of the reasons we need thread safety -- otherwise,
a second thread making calls while a first one has turned off the magic
will bypass the magic unintentionally. (Interestingly, if this imposes
a performance cost, I can't measure it.)
What were the arguments against implementing this with FUSE?

Long story. Basically, it turns out to be very useful to be able to do
this in arbitrary directories without requiring special hackery, and as
I recall, FUSE requires that you have at SOME point had root privileges
to set up the mounting. This one can be configured, installed, and used
without ever having any special privileges. Also, I think we support at
least one host without FUSE support!
Also, how does the client start a server? Does it fork()? Or by
posix_spawn()? Or by messaging a super-server?

The client forks.

-s
 
E

Ersek, Laszlo

I don't. :)

What I have is:

1. I define a function named open() with the same signature as the syscall.
2. I define a function named dummy_open() with that same signature, too.
3. I define a pointer-to-function named real_open() with the same signature.
4. I define a function named wrap_open() with the same signature.

I was calling your pointer-to-function named real_open "orig_open". How
do you initialize it? :)


Thank you for the description,
lacos
 
A

Alan Curry

|
|What this ends up doing is allowing me to intercept every system call that
|deals with the filesystem and is affected by privileges, and act as though
|you had root privileges, or at least, make it look as though it acted as

Somebody already did this. It's called fakeroot. Terrible duplication of
effort if you reinvented it from scratch
 
S

Seebs

I was calling your pointer-to-function named real_open "orig_open". How
do you initialize it? :)

With a magic bit of code using "dlsym()" to look up symbols in other
libraries, relying on a feature where it's possible to specify "give me
the next version after the one you already found". :)

-s
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,201
Latest member
KourtneyBe

Latest Threads

Top