Please wait until the page is fully downloaded and then press the "Expand" button or the blue line numbers.

0006001 /*       this file contains the interface of the network software with rest of
0006002          minix. Furthermore it contains the main loop of the network task.
0006003 
0006004 Copyright 1995 Philip Homburg
0006005 
0006006 The valid messages and their parameters are:
0006007 
0006008 from FS:
0006009  __________________________________________________________________
0006010 |               |           |         |       |          |         |
0006011 | m_type        | DEVICE    | PROC_NR | COUNT | POSITION | ADDRESS |
0006012 |_______________|___________|_________|_______|__________|_________|
0006013 |               |           |         |       |          |         |
0006014 | NW_OPEN       | minor dev | proc nr | mode  |          |         |
0006015 |_______________|___________|_________|_______|__________|_________|
0006016 |               |           |         |       |          |         |
0006017 | NW_CLOSE      | minor dev | proc nr |       |          |         |
0006018 |_______________|___________|_________|_______|__________|_________|
0006019 |               |           |         |       |          |         |
0006020 | NW_IOCTL      | minor dev | proc nr |       | NWIO..   | address |
0006021 |_______________|___________|_________|_______|__________|_________|
0006022 |               |           |         |       |          |         |
0006023 | NW_READ       | minor dev | proc nr | count |          | address |
0006024 |_______________|___________|_________|_______|__________|_________|
0006025 |               |           |         |       |          |         |
0006026 | NW_WRITE      | minor dev | proc nr | count |          | address |
0006027 |_______________|___________|_________|_______|__________|_________|
0006028 |               |           |         |       |          |         |
0006029 | NW_CANCEL     | minor dev | proc nr |       |          |         |
0006030 |_______________|___________|_________|_______|__________|_________|
0006031 
0006032 from the Ethernet task:
0006033  _______________________________________________________________________
0006034 |               |           |         |          |            |         |
0006035 | m_type        | DL_PORT   | DL_PROC | DL_COUNT | DL_STAT    | DL_TIME |
0006036 |_______________|___________|_________|__________|____________|_________|
0006037 |               |           |         |          |            |         |
0006038 | DL_TASK_INT   | minor dev | proc nr | rd_count | 0   | stat | time    |
DL_TASK_INT is not correct. Instead, it should be DL_INIT_REPLY, as is found in the code of dp8390.c (the implementation of the ethernet task). Unfortunately, in the ascii graphic in the comments at the beginning of dp8390.c, there is another typo in which DL_INIT_REPLY is referred to as DL_INIT_REPL.


0006039 |_______________|___________|_________|__________|____________|_________|
0006040 |               |           |         |          |            |         |
0006041 | DL_TASK_REPLY | minor dev | proc nr | rd_count | err | stat | time    |
0006042 |_______________|___________|_________|__________|____________|_________|
0006043 */
0006044 
From dev(4):

"The TCP/IP task is not a kernel task, but a separate server. It starts its life as a normal process named inet, executes a few system calls to become a server like MM and FS, and then signs up with FS to manage the TCP/IP devices that happen to be assigned major 7. There is a kernel task involved in this scenario though, the ethernet boards are managed by the kernel task DP8390. (So the TCP/IP task is not completely freestanding.)"

inet.c contains main(), a function that (under normal circumstances) never returns. A flowchart for main() is shown below (to see the details for a function, click on the function's name).

main() Flowchart







0006045 #include "inet.h"
0006046 
0006047 #define _MINIX       1
_MINIX

_MINIX is a macro that can affect whether certain lines within include files are actually included by the preprocessor. For example, if a file #include's <string.h> (as inet_config.c does), then the following lines containing prototypes that enable backward compatibility are included by the preprocessor:

#ifdef _MINIX

/* For backward compatibility. */
_PROTOTYPE( char *index, (const char *_s, int _charwanted) );
_PROTOTYPE( char *rindex, (const char *_s, int _charwanted) );
_PROTOTYPE( void bcopy, (const void *_src, void *_dst, size_t _length) );
_PROTOTYPE( int bcmp, (const void *_s1, const void *_s2, size_t _length));
_PROTOTYPE( void bzero, (void *_dst, size_t _length) );
_PROTOTYPE( void *memccpy, (char *_dst, const char *_src, int _ucharstop,
size_t _size) );
/* Misc. extra functions */
_PROTOTYPE( int strcasecmp, (const char *_s1, const char *_s2) );
_PROTOTYPE( int strncasecmp, (const char *_s1, const char *_s2,
size_t _len) );
_PROTOTYPE( size_t strnlen, (const char *_s, size_t _n) );
#endif
Note that nearly all macros that begin with an underscore (_) are specific to library routines.

The 12th paragraph of section 2.6.2, lines 01184-011200, and lines 01241-01245 in the Minix code in Operating Systems, Design and Implementation describe the _MINIX macro.


0006048 
0006049 #include <unistd.h>
0006050 #include <sys/svrctl.h>
0006051 
0006052 #include "mq.h"
0006053 #include "proto.h"
0006054 #include "generic/type.h"
0006055 
0006056 #include "generic/assert.h"
0006057 #include "generic/buf.h"
0006058 #include "generic/clock.h"
0006059 #include "generic/eth.h"
0006060 #include "generic/event.h"
0006061 #if !CRAMPED
CRAMPED

CRAMPED is #define'd in inet.h as 0 for an 80836 processor or better and 1 for anything less (e.g., 8086). In the figures and discussions in this documentation, it is assumed that the system is an 80836 or better.

If the system is not even an 80386, it is a fair assumption that there is not much memory to work with (i.e., the memory is "cramped").


0006062 #include "generic/arp.h"
0006063 #include "generic/ip.h"
0006064 #include "generic/psip.h"
0006065 #include "generic/sr.h"
0006066 #include "generic/tcp.h"
0006067 #include "generic/udp.h"
0006068 #endif
0006069 
0006070 THIS_FILE
THIS_FILE is #define'd in inet.h:

#define THIS_FILE static char *this_file= __FILE__;

The preprocessor expands macro __FILE__ to the filename of the current file (in the form of a C string constant) and this becomes the value of variable this_file.

this_file is used in many of the diagnostic functions. For example, ip_panic() uses the variable this_file.


0006071 
0006072 int this_proc;              /* Process number of this server. */
0006073 int synal_tasknr;       /* Task number of the synchronous alarm task. */
0006074 
0006075 #ifdef BUF_CONSISTENCY_CHECK
0006076 extern int inet_buf_debug;
0006077 #endif
0006078 
0006079 _PROTOTYPE( void main, (void) );
0006080 
0006081 FORWARD _PROTOTYPE( void nw_init, (void) );
0006082 
0006083 PUBLIC void main()
0006084 {
0006085          mq_t *mq;
mq_list / mq_freelist / mq_init() / mq_get() / mq_free()

Messages that the network service receives from the other services (file system, memory manager, etc.) and the kernel are placed into a linked list. This linked list is formed with the following struct:

typedef struct mq

{
message mq_mess;
struct mq *mq_next;
int mq_allocated;
} mq_t;
and is initialized by mq_init(). After initialization, the linked list is as follows:



As messages are received, mq_get() is called to acquire the element of mq_list[] pointed to by mq_freelist. This element holds the message until the message is processed. mq_allocated for this element is set to 1 and mq_freelist is advanced to point to the next element.

After the message is processed, mq_free() places the element back onto mq_freelist and sets mq_allocated back to 0.

As with other queues within the network service, the memory for the message queue comes from an array with a limited number of elements. This must be done because memory within Minix is scarce (for one reason, Minix does not have virtual memory).

Click here for a description of the different types of messages.


0006086          int r;
0006087          int source;
0006088 
0006089   DBLOCK(1, printf("%s\n", version));
Debugging functions and macros are not covered in the documentation.


0006090 
0006091          nw_init();
nw_init()

nw_init() is called by main() to initialize the network service before entering the endless loop within main(). nw_init() first calls read_conf() to process the /etc/inet.conf file and then calls svrctl() three times to register the network service with the memory manager, the kernel, and the file system. Finally, nw_init() calls the initialization functions of several subsystems (e.g., the buffer management subsystem) and protocols (e.g., udp_init() for the udp layer).


0006092          while (TRUE)
The beginning of main()'s endless loop.


0006093          {
0006094 #ifdef BUF_CONSISTENCY_CHECK
Debugging functions and macros are not covered in the documentation.


0006095                   if (inet_buf_debug)
0006096                   {
0006097                            static int buf_debug_count= 0;
0006098 
0006099                            if (buf_debug_count++ > inet_buf_debug)
0006100                            {
0006101                                     buf_debug_count= 0;
0006102                                     if (!bf_consistency_check())
0006103                                              break;
0006104                            }
0006105                   }
0006106 #endif
0006107                   if (ev_head)
event_t / ev_enqueue() / ev_process() / ev_init() / ev_in_queue()

The event_t typedef is declared in inet/generic/event.h:

typedef struct event

{
ev_func_t ev_func;
ev_arg_t ev_arg;
struct event *ev_next;
} event_t;
If an event needs to be scheduled, ev_enqueue() is called to place the event in the system-wide event queue whose head is ev_head. ev_process() is eventually called from the main loop in inet.c to process the events. ev_in_queue(ev) simply returns TRUE if the event ev, ev_in_queue()'s only parameter, has a non-null value for func (see below) and FALSE if func is null. In this way, ev_in_queue() determines whether the event has been configured.

ev_init(ev) simply zeroes out the ev_func and ev_next fields of the event ev, ev_init()'s only parameter.

ev_func: A function (e.g., ip_process_loopb()) that performs some task.

ev_arg:

typedef union ev_arg

{
int ev_int;
void *ev_ptr;
} ev_arg_t;
ev_arg is ev_func's argument. In the case of a packet destined for the loopback address (127.0.0.1), the argument will be the ip port associated with the ip file descriptor that is sending out the packet. In the case of a message from the ethernet task that caused a deadlock, ev_arg is a pointer to the message's destination ethernet port.

ev_next: The next event in the system-wide event queue.


0006108                   {
0006109                            ev_process();
event_t / ev_enqueue() / ev_process() / ev_init() / ev_in_queue()

The event_t typedef is declared in inet/generic/event.h:

typedef struct event

{
ev_func_t ev_func;
ev_arg_t ev_arg;
struct event *ev_next;
} event_t;
If an event needs to be scheduled, ev_enqueue() is called to place the event in the system-wide event queue whose head is ev_head. ev_process() is eventually called from the main loop in inet.c to process the events. ev_in_queue(ev) simply returns TRUE if the event ev, ev_in_queue()'s only parameter, has a non-null value for func (see below) and FALSE if func is null. In this way, ev_in_queue() determines whether the event has been configured.

ev_init(ev) simply zeroes out the ev_func and ev_next fields of the event ev, ev_init()'s only parameter.

ev_func: A function (e.g., ip_process_loopb()) that performs some task.

ev_arg:

typedef union ev_arg

{
int ev_int;
void *ev_ptr;
} ev_arg_t;
ev_arg is ev_func's argument. In the case of a packet destined for the loopback address (127.0.0.1), the argument will be the ip port associated with the ip file descriptor that is sending out the packet. In the case of a message from the ethernet task that caused a deadlock, ev_arg is a pointer to the message's destination ethernet port.

ev_next: The next event in the system-wide event queue.


0006110                            continue;
0006111                   }
0006112                   if (clck_call_expire)
clck_call_expire

clck_call_expire is a global variable that is set by set_timer() if a timer has expired. If clck_call_expire is set, clck_expire_timers() is called the next time around main()'s endless loop. clck_expire_timers(), in turn, calls the client-specific code that handles an alarm (e.g., arp_timeout().


0006113                   {
0006114                            clck_expire_timers();
clck_expire_timers()

clck_expire_timers() is called in the endless loop in main() if one or more timers have expired. clck_expire_timers() calls the client-specific timeout function (e.g., arp_timeout()) to handle the time-out and then calls set_timer() to request another alarm from the synchronous alarm task (if there are other timers in timer_chain).


0006115                            continue;
0006116                   }
0006117                   mq= mq_get();
mq_get()

mq_get() returns a pointer to the next available element of mq_list[] on mq_freelist.



Click here for a detailed description of mq_list[] and mq_freelist.


0006118                   if (!mq)
If there are no more messages in mq_freelist, there's a big problem. Under normal circumstances, this shouldn't occur since user processes can issue only one I/O (read/write/ioctl) request at a time; the default number of processes in Minix is only 32 while the number of messages in mq_list is 128.


0006119                            ip_panic(("out of messages"));
ip_panic()

ip_panic() (for the case where the system is not "CRAMPED") is #define'd as follows:

#define ip_panic(print_list) (panic0(this_file, __LINE__), printf print_list, panic())

panic0() simply prints out an error message indicating where the panic was called and panic() does a stacktrace before calling sys_abort() to bring the system down.


0006120 
0006121                   r= receive (ANY, &mq->mq_mess);
Receive the next message. This message will be from either the file system, the Synchronous Alarm task, or the ethernet driver.

Click here for a detailed description of the Minix messaging system.


0006122                   if (r<0)
0006123                   {
0006124                            ip_panic(("unable to receive: %d", r));
ip_panic()

ip_panic() (for the case where the system is not "CRAMPED") is #define'd as follows:

#define ip_panic(print_list) (panic0(this_file, __LINE__), printf print_list, panic())

panic0() simply prints out an error message indicating where the panic was called and panic() does a stacktrace before calling sys_abort() to bring the system down.


0006125                   }
0006126                   reset_time();
reset_time()

In the interest of conserving processor time, the time (as seen by the network service) is "frozen" for certain intervals. In this way, the current time, if needed, does not need to be retrieved from the system (which is fairly costly in terms of processor time).

reset_time() simply sets the global variable curr_time to zero (i.e., makes curr_time invalid). If curr_time is zero, the next time that get_time() is called, get_time() must retrieve the time by requesting the time from the clock task.

reset_time() is called each time around main()'s endless loop.


0006127                   source= mq->mq_mess.m_source;
0006128                   if (source == FS_PROC_NR)
The message is from the file system. Call sr_rec() to process the message.


0006129                   {
0006130                            sr_rec(mq);
Note that if the message is from the file system, mq_free() is not called to free the message. sr_rec() frees the message itself.


sr_rec()


The network service receives messages from the file system (FS), the ethernet task, the clock task, and the asynchronous alarm task. sr_rec() is called upon the receipt of a message from the file system in the endless loop within main().

If there are any messages in repl_queue, sr_rec() first calls sr_repl_queue() to process the messages in repl_queue before calling sr_open() (for open requests), sr_close() (for close requests), sr_rwio() (for read, write, and io requests), or sr_cancel() (for cancel requests) to further process the message.

Note that "rec" in "sr_rec" stands for "receive".


0006131                   }
0006132                   else if (source == synal_tasknr)
synal_tasknr is the task number of the Synchronous Alarm task and is set on line 6188 of the initialization function nw_init().


synchronous alarm task


The network service listens for messages from 3 sources: the file system service, the synchronous alarm task, and the ethernet task. The function of the synchronous alarm task is described in Operating Systems, Design and Implementation by Tanenbaum and Woodhull, page 229:

"The synchronous alarm mechanism was added to MINIX to support the network server, which like the memory manager and the file server, runs as a separate process. Frequently there is a need to set a limit on the time a process may be blocked while waiting for input. For instance in a network, failure to receive an acknowledgement of a data packet within a definite period is probably due to a failure of transmission. A network server can set a synchronous alarm before it tries to receive a message and blocks. Since the synchronous alarm is delivered as a message, it will unblock the server eventually if no message is received from the network. Upon receiving any message the server must first reset the alarm. Then by examining the type or origin of the message, it can determine a packet has arrived or if it has been unblocked by a timeout. If it is the latter, then the server can try to recover, usually by resending the last unacknowledged packet."

In other words, the synchronous alarm task prevents client connections (specifically, tcp and arp connections) from hanging.

The synchronous alarm works as follows:

If a client wishes to set a synchronous alarm, clck_timer() is called and a timer is added to the existing timers. For example, the arp client calls clck_timer() in order to set a limit on the time it's willing to wait for another system on the network to respond to an arp request. If the new timer expires before any of the other timers are due to expire, a message is sent to the clock task requesting an alarm set for the appropriate time. (Note that the clock task is only aware of at most one alarm at any given time.)

When the alarm message is eventually received by the network service (this process), set_timer() sets the global variable clck_call_expire and returns. The next time around its endless while loop, main() processes the received alarm message by calling clck_expire_timers(). This function calls the client-specific function (e.g., arp_timeout()) that handles alarms, passing in the necessary information so that the client can identify the connection associated with the alarm.

After handling the alarm, set_timer() again sends a message to the clock task requesting an alarm message be sent for the next timer due to expire (assuming that there are other timers in timer_chain).


0006133                   {
0006134                            clck_tick (&mq->mq_mess);
clck_tick()

clck_tick() is called upon receipt of a message from the synchronous alarm task in the endless loop within main(). clck_tick() simply sets next_timeout to zero (0) and then calls set_timer(), which sets clck_call_expire if a timer has expired, causing clck_expire_timers() to be called. clck_expire_timers()'s most important task is to call the client-specific timeout function (e.g., arp_timeout()) to handle the time-out. clck_expire_timer() also calls set_timer() to set another timer with the synchronous alarm task.


0006135                            mq_free(mq);
mq_free()

mq_free(mq) places mq, mq_free()'s only parameter, back on mq_freelist.



Click here for a detailed description of mq_list[] and mq_freelist.


0006136                   }
0006137                   else
0006138                   {
0006139 compare(mq->mq_mess.m_type, ==, DL_TASK_REPLY);
If the message is not from the file system (lines 6128-6131) or the synchronous alarm task (lines 6132-6136), the message must be from the ethernet task (driver) and the message must be of type DL_TASK_REPLY (in other words, the ethernet task is responding to a previous request message).


compare()


compare is #define'd in inet/generic/assert.h:

#define compare(a,t,b) (!((a) t (b)) ? bad_compare(this_file, __LINE__, \
(a), #a " " #t " " #b, (b)) : (void) 0)

and bad_compare() is defined in inet/inet.c.

If the relationship between the 3 arguments in compare() does not hold, some debugging output is emitted and then Minix is terminated.

For example, if compare(result, >=, 0) is called and result (the first argument) is -1, Minix will be terminated.


0006140                            eth_rec(&mq->mq_mess);
eth_rec()

eth_rec() is called from the main loop or by eth_get_stat() or by eth_set_rec_conf() upon receipt of a message from the ethernet task. If the message is valid, the message will either indicate that a write operation has successfully ended (in which case, write_int() is called) or that the ethernet task has received a packet (in which case, read_int() is called).

The message (which is of type mess_2) has the following structure:
From dp8390.c:


*
* m-type DL_PORT DL_PROC DL_COUNT DL_STAT DL_CLCK
* |------------|----------|---------|----------|---------|---------|
* |DL_TASK_REPL| port nr | proc nr | rd-count | err|stat| clock |
* |------------|----------|---------|----------|---------|---------|
*

From include/minix/type.h:

typedef struct {
int m_source; /* who sent the message */
int m_type; /* what kind of message is it */
union {
mess_1 m_m1;
mess_2 m_m2;
mess_3 m_m3;
mess_4 m_m4;
mess_5 m_m5;
mess_6 m_m6;
} m_u;
} message;

typedef struct {int m2i1, m2i2, m2i3; long m2l1, m2l2; char *m2p1;} mess_2;

From include/minix/com.h:

# define DL_PORT m2_i1
# define DL_PROC m2_i2
# define DL_COUNT m2_i3
# define DL_CLCK m2_l2
# define DL_STAT m2_l1



0006141                            mq_free(mq);
mq_free()

mq_free(mq) places mq, mq_free()'s only parameter, back on mq_freelist.



Click here for a detailed description of mq_list[] and mq_freelist.


0006142                   }
0006143          }
0006144          ip_panic(("task is not allowed to terminate"));
ip_panic()

ip_panic() (for the case where the system is not "CRAMPED") is #define'd as follows:

#define ip_panic(print_list) (panic0(this_file, __LINE__), printf print_list, panic())

panic0() simply prints out an error message indicating where the panic was called and panic() does a stacktrace before calling sys_abort() to bring the system down.


0006145 }
0006146 
0006147 PRIVATE void nw_init()
nw_init()

nw_init() is called by main() to initialize the network service before entering the endless loop within main(). nw_init() first calls read_conf() to process the /etc/inet.conf file and then calls svrctl() three times to register the network service with the memory manager, the kernel, and the file system. Finally, nw_init() calls the initialization functions of several subsystems (e.g., the buffer management subsystem) and protocols (e.g., udp_init() for the udp layer).


0006148 {
0006149          struct fssignon device;
0006150          struct systaskinfo info;
0006151 
0006152          /* Read configuration. */
0006153          read_conf();
eth_conf[] / psip_conf[] / ip_conf[] / iftype[]

read_conf(), called by nw_init(), reads the configuration file /etc/inet.conf and populates the arrays eth_conf[], psip_conf[], ip_conf[] and iftype[].

An example inet.conf file and the resulting arrays are shown in the following figure:



Note that "psip" stands for "pseudo ip", which is generally used over serial lines (as opposed to ethernet).

A fourth array, iftype[], correlates the interface type with the interface number (the index of the array). Since the same information in if_type[] can be found in ip_conf[], the code could have been rewritten using ip_conf[] where if_type[] was used. However, this would have limited the flexibility of the code since a network protocol other than ip could be added later.


iftype[]
0th entry1st entry2nd entry3rd entry
interface typeNETTYPE_ETH NETTYPE_PSIP0 0


In addition to populating the four arrays, read_conf() also creates the necessary device files. For example, for the inet.conf file above, the following files (and their minor device numbers in parentheses) are created:

/dev/eth0 (1), /dev/ip0 (2), /dev/tcp0 (3), /dev/udp0 (4)
/dev/psip1 (17), /dev/ip1 (18), /dev/tcp1 (19), /dev/udp1 (20)

Note that the major device number for all of the network device files is set to the major device number of the /dev/ip file, which must be created before the network service can be started. The value of this major device number is 7 for the default configuration.

Links are also created for the default entry. For example, for the inet.conf file above, the following links are created:

/dev/eth -> /dev/eth0
/dev/ip -> /dev/ip0
/dev/tcp -> /dev/tcp0
/dev/udp -> /dev/udp0


From inet(8):


Inet starts as a normal process, reads the configuration file
/etc/inet.conf to see what it should do, and uses a few special low level
system calls to turn itself into a server. The format of the
configuration file is as follows:

Configuration
The inet configuration file is fairly simple, here is an example:

eth0 DP8390 0 { default; };
psip1;

It tells that network 0 (the one containing devices eth0, ip0, tcp0 and
udp0) uses the ethernet device driver handled by task "DP8390" at port 0.
This network is marked as the default network, so most programs use it
through the unnumbered devices like /dev/tcp or /dev/udp. Network 1 is a
Pseudo IP network that can be used for a serial IP over a modem for
instance.

ethN task port {options};
This sets up an ethernet with device name /dev/ethN, built on the
given ethernet device driver at the given port at that driver. (If
a network driver manages two network cards then they are at port 0
and 1.)


psipN {options};
Creates pseudo IP network /dev/psipN, usable for IP over serial
lines, tunnels and whatnot.



0006154          eth_prep();
eth_prep()

eth_prep() simply allocates memory for the ethernet port table. The ethernet ports (as well as the ethernet file descriptors) are later initialized by eth_init().


0006155          arp_prep();
arp_prep()

arp_prep() simply allocates memory for the arp port table. The arp ports are later initialized by arp_init().


0006156          psip_prep();
The psip ("PSeudo IP") functions and files are not covered by this web site.


0006157          ip_prep();
ip_prep()

ip_prep() allocates memory for ip_port_table[] before calling icmp_prep().

ip_prep() is called a single time during the initialization of the network service. ip_prep() is called by nw_init().


0006158          tcp_prep();
The tcp layer is also not covered by this web site.


0006159          udp_prep();
udp_prep()

udp_prep() allocates memory for udp_port_table[], whose length is equal to the number of interfaces configured. For the following inet.conf file:

eth0 DP8390 0 { default; };
psip1;

there will be 2 interfaces, one for the ethernet port and one for the psip port.

udp_prep() is called a single time from inet.c.


0006160 
0006161          /* Sign on as a server at all offices in the proper order. */
From dev(4):

"The TCP/IP task is not a kernel task, but a separate server. It starts its life as a normal process named inet, executes a few system calls to become a server like MM and FS, and then signs up with FS to manage the TCP/IP devices that happen to be assigned major 7."


0006162          if (svrctl(MMSIGNON, (void *) NULL) == -1) {
Register the network service as a server with the memory manager (MM).

A description of how this is accomplished is within the general comment below.


svrctl()


From svrctl(2):

Svrctl allows root to control the kernel in various ways or implements some very Minix specific system calls that don't deserve their own system call number.

This system call makes it easy to add new ways of setting and getting kernel parameters, but at the same time, backwards compatibility is not guaranteed. Read the include file to see what the struct's mentioned below look like. Most calls are root-only, unless specified otherwise.

The only way to know how to properly use these calls is to study the associated kernel or server code, or the programs that already use these calls.


Here is a description of the different requests made by the network service (from svrctl(2)):

MMSIGNON
Inform MM that the current process wants to become a server.

FSSIGNON
Register a new device with FS.

SYSSIGNON
Inform the kernel that the process want to become a server. The processes task number is filled-in in a struct systaskinfo.



In order to understand svrctl() better, it is helpful to look at some code. Here is the relevant code for the call from the network service that registers itself as a server to the memory manager. In other words, this is the relevant code for the svrctl(MMSIGNON, (void *) NULL) call that the network service makes:


From include/sys/svrctl.h:

#define MMSIGNON _IO ('M', 4)

#define _IO(x,y) ((x << 8) | y | _IOC_VOID)

#define _IOC_VOID 0x20000000


Therefore,

MMSIGNON = _IO ('M', 4) = ( (0x4D<<8) | 4 | 0x20000000 )
= 0x4D00 | 4 | 0x20000000
= 0x20004D04


From src/lib/other/_svrctl.c:

int svrctl(int request, void *argp)

{
message m;

m.m2_i1 = request;
m.m2_p1 = argp;

switch ((request >> 8) & 0xFF) {
case 'M':
case 'S':
/* MM handles calls for itself and the kernel. */
return _syscall(MM, SVRCTL, &m);
case 'F':
case 'I':
/* FS handles calls for itself and inet. */
return _syscall(FS, SVRCTL, &m);
default:
errno = EINVAL;
return -1;
}
}
(Note that MM is #define'd as 0 and FS is #define'd as 1 in include/lib.h.)


Since (0x20004D04 >> 8) & 0xFF = 0x4D = 'M', _syscall(MM, SVRCTL, &m) is called. _syscall(), in turn, calls _sendrec(). _sendrec() is not covered in this general comment but in its own general comment.

From src/lib/other/syscall.c:

PUBLIC int _syscall(who, syscallnr, msgptr)

int who;
int syscallnr;
register message *msgptr;
{
int status;

msgptr->m_type = syscallnr;
status = _sendrec(who, msgptr);
if (status != 0) {
/* 'sendrec' itself failed. */
/* XXX - strerror doesn't know all the codes */
msgptr->m_type = status;
}
if (msgptr->m_type < 0) {
errno = -msgptr->m_type;
return(-1);
}
return(msgptr->m_type);
}
Through the normal message passing system, a message is sent to the memory manager from the network service informing the memory manager of the network service's status as a server (in other words, the network service signs on to the memory manager as a server; a server has greater privileges than a normal process). This message is handled by the relevant function in call_vec[], an array of functions.

From src/mm/main.c:

/*===========================================================================*

* main *
*===========================================================================*/
PUBLIC void main()
{
/* Main routine of the memory manager. */

int result, proc_nr;
struct mproc *rmp;

mm_init(); /* initialize memory manager tables */

/* This is MM's main loop- get work and do it, forever and forever. */
while (TRUE) {
get_work(); /* wait for an MM system call */

/* If the call number is valid, perform the call. */
if ((unsigned) mm_call >= NCALLS) {
result = ENOSYS;
} else {
result = (*call_vec[mm_call])();
}

/* Send the results back to the user to indicate completion. */
if (result != E_NO_MESSAGE) setreply(who, result);

swap_in(); /* maybe a process can be swapped in? */

/* Send out all pending reply messages, including the answer to
* the call just made above. The processes must not be swapped out.
*/
for (proc_nr = 0, rmp = mproc; proc_nr < NR_PROCS; proc_nr++, rmp++) {
if ((rmp->mp_flags & (REPLY | ONSWAP)) == REPLY) {
if (send(proc_nr, &rmp->mp_reply) != OK)
panic("MM can't reply to", proc_nr);
rmp->mp_flags &= ~REPLY;
}
}
}
}


get_work() has two important tasks - to receive the incoming message and set the global variable mm_call to the message type.

/*===========================================================================*

* get_work *
*===========================================================================*/
PRIVATE void get_work()
{
/* Wait for the next message and extract useful information from it. */

if (receive(ANY, &mm_in) != OK) panic("MM receive error", NO_NUM);
who = mm_in.m_source; /* who sent the message */
mm_call = mm_in.m_type; /* system call number */

/* Process slot of caller. Misuse MM's own process slot for tasks (KSIG?). */
mp = &mproc[who < 0 ? MM_PROC_NR : who];
}
From src/mm/table.c:

_PROTOTYPE (int (*call_vec[NCALLS]), (void) ) = {

no_sys, /* 0 = unused */
do_mm_exit, /* 1 = exit */
do_fork, /* 2 = fork */
no_sys, /* 3 = read */
no_sys, /* 4 = write */
no_sys, /* 5 = open */
no_sys, /* 6 = close */
do_waitpid, /* 7 = wait */
no_sys, /* 8 = creat */
no_sys, /* 9 = link */
no_sys, /* 10 = unlink */
do_waitpid, /* 11 = waitpid */
no_sys, /* 12 = chdir */
no_sys, /* 13 = time */
no_sys, /* 14 = mknod */
no_sys, /* 15 = chmod */
no_sys, /* 16 = chown */
do_brk, /* 17 = break */
no_sys, /* 18 = stat */
no_sys, /* 19 = lseek */
do_getset, /* 20 = getpid */
no_sys, /* 21 = mount */
no_sys, /* 22 = umount */
do_getset, /* 23 = setuid */
do_getset, /* 24 = getuid */
no_sys, /* 25 = stime */
do_trace, /* 26 = ptrace */
do_alarm, /* 27 = alarm */
no_sys, /* 28 = fstat */
do_pause, /* 29 = pause */
no_sys, /* 30 = utime */
no_sys, /* 31 = (stty) */
no_sys, /* 32 = (gtty) */
no_sys, /* 33 = access */
no_sys, /* 34 = (nice) */
no_sys, /* 35 = (ftime) */
no_sys, /* 36 = sync */
do_kill, /* 37 = kill */
no_sys, /* 38 = rename */
no_sys, /* 39 = mkdir */
no_sys, /* 40 = rmdir */
no_sys, /* 41 = dup */
no_sys, /* 42 = pipe */
no_sys, /* 43 = times */
no_sys, /* 44 = (prof) */
no_sys, /* 45 = unused */
do_getset, /* 46 = setgid */
do_getset, /* 47 = getgid */
no_sys, /* 48 = (signal)*/
no_sys, /* 49 = unused */
no_sys, /* 50 = unused */
no_sys, /* 51 = (acct) */
no_sys, /* 52 = (phys) */
no_sys, /* 53 = (lock) */
no_sys, /* 54 = ioctl */
no_sys, /* 55 = fcntl */
no_sys, /* 56 = (mpx) */
no_sys, /* 57 = unused */
no_sys, /* 58 = unused */
do_exec, /* 59 = execve */
no_sys, /* 60 = umask */
no_sys, /* 61 = chroot */
do_getset, /* 62 = setsid */
do_getset, /* 63 = getpgrp */

do_ksig, /* 64 = KSIG: signals originating in the kernel */
no_sys, /* 65 = UNPAUSE */
no_sys, /* 66 = unused */
no_sys, /* 67 = REVIVE */
no_sys, /* 68 = TASK_REPLY */
no_sys, /* 69 = unused */
no_sys, /* 70 = unused */
do_sigaction, /* 71 = sigaction */
do_sigsuspend, /* 72 = sigsuspend */
do_sigpending, /* 73 = sigpending */
do_sigprocmask, /* 74 = sigprocmask */
do_sigreturn, /* 75 = sigreturn */
do_reboot, /* 76 = reboot */
do_svrctl, /* 77 = svrctl */
};
From src/mm/misc.c:

*=====================================================================*

* do_svrctl *
*=====================================================================*/
PUBLIC int do_svrctl()
{
int req;
vir_bytes ptr;

req = svrctl_req;
ptr = (vir_bytes) svrctl_argp;

/* Is the request for the kernel? */
if (((req >> 8) & 0xFF) == 'S') {
return(sys_sysctl(who, req, mp->mp_effuid == SUPER_USER, ptr));
}

switch(req) {
case MMSIGNON: {
/* A user process becomes a task. Simulate an exit by
* releasing a waiting parent and disinheriting children.
*/
struct mproc *rmp;
pid_t pidarg;

if (mp->mp_effuid != SUPER_USER) return(EPERM);

rmp = &mproc[mp->mp_parent];
tell_fs(EXIT, who, 0, 0);

pidarg = rmp->mp_wpid;
if ((rmp->mp_flags & WAITING) && (pidarg == -1
|| pidarg == mp->mp_pid || -pidarg == mp->mp_procgrp))
{
/* Wake up the parent. */
rmp->reply_res2 = 0;
setreply(mp->mp_parent, mp->mp_pid);
rmp->mp_flags &= ~WAITING;
}

/* Disinherit children. */
for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++) {
if (rmp->mp_flags & IN_USE && rmp->mp_parent == who) {
rmp->mp_parent = INIT_PROC_NR;
}
}

/* Become like MM and FS. */
mp->mp_pid = mp->mp_procgrp = 0;
mp->mp_parent = 0;
return(OK); }

case MMSWAPON: {
struct mmswapon swapon;

if (mp->mp_effuid != SUPER_USER) return(EPERM);

if (sys_copy(who, D, (phys_bytes) ptr,
MM_PROC_NR, D, (phys_bytes) &swapon,
(phys_bytes) sizeof(swapon)) != OK) return(EFAULT);

return(swap_on(swapon.file, swapon.offset, swapon.size)); }

case MMSWAPOFF: {
if (mp->mp_effuid != SUPER_USER) return(EPERM);

return(swap_off()); }

default:
return(EINVAL);
}
}
For an MMSIGNON request, the mp_pid (servers do not have pid's), mp_procgrp (servers are in process group 0; process groups are used for signals), and mp_parent (servers do not have parents) fields of the process must be set to 0, any parents of the process must be released (i.e., the current process is the child of fork() and the parent of the process called wait()), and any children of the process must be adopted by init.


0006163     printf("inet: server signon failed\n");
0006164                   exit(1);
Note that if the registration with the memory manager fails, exit() is called. However, if the registration with the kernel (line 6166) or file system (line 6177) fail, pause() is called. Since the network service is partially configured as a server if the first call to svrctl() (i.e., the registration with the memory manager) succeeds, it cannot simply call exit() as a user process would.


0006165          }
0006166          if (svrctl(SYSSIGNON, (void *) &info) == -1) pause();
Register the network service with the kernel.

This system call places the process number of the network service into the variable info (this_proc is set to this value on the next line of code).

For the SYSSIGNON request, do_sysctl() in src/kernel/system.c is ultimately called.


svrctl()


From svrctl(2):

Svrctl allows root to control the kernel in various ways or implements some very Minix specific system calls that don't deserve their own system call number.

This system call makes it easy to add new ways of setting and getting kernel parameters, but at the same time, backwards compatibility is not guaranteed. Read the include file to see what the struct's mentioned below look like. Most calls are root-only, unless specified otherwise.

The only way to know how to properly use these calls is to study the associated kernel or server code, or the programs that already use these calls.


Here is a description of the different requests made by the network service (from svrctl(2)):

MMSIGNON
Inform MM that the current process wants to become a server.

FSSIGNON
Register a new device with FS.

SYSSIGNON
Inform the kernel that the process want to become a server. The processes task number is filled-in in a struct systaskinfo.



In order to understand svrctl() better, it is helpful to look at some code. Here is the relevant code for the call from the network service that registers itself as a server to the memory manager. In other words, this is the relevant code for the svrctl(MMSIGNON, (void *) NULL) call that the network service makes:


From include/sys/svrctl.h:

#define MMSIGNON _IO ('M', 4)

#define _IO(x,y) ((x << 8) | y | _IOC_VOID)

#define _IOC_VOID 0x20000000


Therefore,

MMSIGNON = _IO ('M', 4) = ( (0x4D<<8) | 4 | 0x20000000 )
= 0x4D00 | 4 | 0x20000000
= 0x20004D04


From src/lib/other/_svrctl.c:

int svrctl(int request, void *argp)

{
message m;

m.m2_i1 = request;
m.m2_p1 = argp;

switch ((request >> 8) & 0xFF) {
case 'M':
case 'S':
/* MM handles calls for itself and the kernel. */
return _syscall(MM, SVRCTL, &m);
case 'F':
case 'I':
/* FS handles calls for itself and inet. */
return _syscall(FS, SVRCTL, &m);
default:
errno = EINVAL;
return -1;
}
}
(Note that MM is #define'd as 0 and FS is #define'd as 1 in include/lib.h.)


Since (0x20004D04 >> 8) & 0xFF = 0x4D = 'M', _syscall(MM, SVRCTL, &m) is called. _syscall(), in turn, calls _sendrec(). _sendrec() is not covered in this general comment but in its own general comment.

From src/lib/other/syscall.c:

PUBLIC int _syscall(who, syscallnr, msgptr)

int who;
int syscallnr;
register message *msgptr;
{
int status;

msgptr->m_type = syscallnr;
status = _sendrec(who, msgptr);
if (status != 0) {
/* 'sendrec' itself failed. */
/* XXX - strerror doesn't know all the codes */
msgptr->m_type = status;
}
if (msgptr->m_type < 0) {
errno = -msgptr->m_type;
return(-1);
}
return(msgptr->m_type);
}
Through the normal message passing system, a message is sent to the memory manager from the network service informing the memory manager of the network service's status as a server (in other words, the network service signs on to the memory manager as a server; a server has greater privileges than a normal process). This message is handled by the relevant function in call_vec[], an array of functions.

From src/mm/main.c:

/*===========================================================================*

* main *
*===========================================================================*/
PUBLIC void main()
{
/* Main routine of the memory manager. */

int result, proc_nr;
struct mproc *rmp;

mm_init(); /* initialize memory manager tables */

/* This is MM's main loop- get work and do it, forever and forever. */
while (TRUE) {
get_work(); /* wait for an MM system call */

/* If the call number is valid, perform the call. */
if ((unsigned) mm_call >= NCALLS) {
result = ENOSYS;
} else {
result = (*call_vec[mm_call])();
}

/* Send the results back to the user to indicate completion. */
if (result != E_NO_MESSAGE) setreply(who, result);

swap_in(); /* maybe a process can be swapped in? */

/* Send out all pending reply messages, including the answer to
* the call just made above. The processes must not be swapped out.
*/
for (proc_nr = 0, rmp = mproc; proc_nr < NR_PROCS; proc_nr++, rmp++) {
if ((rmp->mp_flags & (REPLY | ONSWAP)) == REPLY) {
if (send(proc_nr, &rmp->mp_reply) != OK)
panic("MM can't reply to", proc_nr);
rmp->mp_flags &= ~REPLY;
}
}
}
}


get_work() has two important tasks - to receive the incoming message and set the global variable mm_call to the message type.

/*===========================================================================*

* get_work *
*===========================================================================*/
PRIVATE void get_work()
{
/* Wait for the next message and extract useful information from it. */

if (receive(ANY, &mm_in) != OK) panic("MM receive error", NO_NUM);
who = mm_in.m_source; /* who sent the message */
mm_call = mm_in.m_type; /* system call number */

/* Process slot of caller. Misuse MM's own process slot for tasks (KSIG?). */
mp = &mproc[who < 0 ? MM_PROC_NR : who];
}
From src/mm/table.c:

_PROTOTYPE (int (*call_vec[NCALLS]), (void) ) = {

no_sys, /* 0 = unused */
do_mm_exit, /* 1 = exit */
do_fork, /* 2 = fork */
no_sys, /* 3 = read */
no_sys, /* 4 = write */
no_sys, /* 5 = open */
no_sys, /* 6 = close */
do_waitpid, /* 7 = wait */
no_sys, /* 8 = creat */
no_sys, /* 9 = link */
no_sys, /* 10 = unlink */
do_waitpid, /* 11 = waitpid */
no_sys, /* 12 = chdir */
no_sys, /* 13 = time */
no_sys, /* 14 = mknod */
no_sys, /* 15 = chmod */
no_sys, /* 16 = chown */
do_brk, /* 17 = break */
no_sys, /* 18 = stat */
no_sys, /* 19 = lseek */
do_getset, /* 20 = getpid */
no_sys, /* 21 = mount */
no_sys, /* 22 = umount */
do_getset, /* 23 = setuid */
do_getset, /* 24 = getuid */
no_sys, /* 25 = stime */
do_trace, /* 26 = ptrace */
do_alarm, /* 27 = alarm */
no_sys, /* 28 = fstat */
do_pause, /* 29 = pause */
no_sys, /* 30 = utime */
no_sys, /* 31 = (stty) */
no_sys, /* 32 = (gtty) */
no_sys, /* 33 = access */
no_sys, /* 34 = (nice) */
no_sys, /* 35 = (ftime) */
no_sys, /* 36 = sync */
do_kill, /* 37 = kill */
no_sys, /* 38 = rename */
no_sys, /* 39 = mkdir */
no_sys, /* 40 = rmdir */
no_sys, /* 41 = dup */
no_sys, /* 42 = pipe */
no_sys, /* 43 = times */
no_sys, /* 44 = (prof) */
no_sys, /* 45 = unused */
do_getset, /* 46 = setgid */
do_getset, /* 47 = getgid */
no_sys, /* 48 = (signal)*/
no_sys, /* 49 = unused */
no_sys, /* 50 = unused */
no_sys, /* 51 = (acct) */
no_sys, /* 52 = (phys) */
no_sys, /* 53 = (lock) */
no_sys, /* 54 = ioctl */
no_sys, /* 55 = fcntl */
no_sys, /* 56 = (mpx) */
no_sys, /* 57 = unused */
no_sys, /* 58 = unused */
do_exec, /* 59 = execve */
no_sys, /* 60 = umask */
no_sys, /* 61 = chroot */
do_getset, /* 62 = setsid */
do_getset, /* 63 = getpgrp */

do_ksig, /* 64 = KSIG: signals originating in the kernel */
no_sys, /* 65 = UNPAUSE */
no_sys, /* 66 = unused */
no_sys, /* 67 = REVIVE */
no_sys, /* 68 = TASK_REPLY */
no_sys, /* 69 = unused */
no_sys, /* 70 = unused */
do_sigaction, /* 71 = sigaction */
do_sigsuspend, /* 72 = sigsuspend */
do_sigpending, /* 73 = sigpending */
do_sigprocmask, /* 74 = sigprocmask */
do_sigreturn, /* 75 = sigreturn */
do_reboot, /* 76 = reboot */
do_svrctl, /* 77 = svrctl */
};
From src/mm/misc.c:

*=====================================================================*

* do_svrctl *
*=====================================================================*/
PUBLIC int do_svrctl()
{
int req;
vir_bytes ptr;

req = svrctl_req;
ptr = (vir_bytes) svrctl_argp;

/* Is the request for the kernel? */
if (((req >> 8) & 0xFF) == 'S') {
return(sys_sysctl(who, req, mp->mp_effuid == SUPER_USER, ptr));
}

switch(req) {
case MMSIGNON: {
/* A user process becomes a task. Simulate an exit by
* releasing a waiting parent and disinheriting children.
*/
struct mproc *rmp;
pid_t pidarg;

if (mp->mp_effuid != SUPER_USER) return(EPERM);

rmp = &mproc[mp->mp_parent];
tell_fs(EXIT, who, 0, 0);

pidarg = rmp->mp_wpid;
if ((rmp->mp_flags & WAITING) && (pidarg == -1
|| pidarg == mp->mp_pid || -pidarg == mp->mp_procgrp))
{
/* Wake up the parent. */
rmp->reply_res2 = 0;
setreply(mp->mp_parent, mp->mp_pid);
rmp->mp_flags &= ~WAITING;
}

/* Disinherit children. */
for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++) {
if (rmp->mp_flags & IN_USE && rmp->mp_parent == who) {
rmp->mp_parent = INIT_PROC_NR;
}
}

/* Become like MM and FS. */
mp->mp_pid = mp->mp_procgrp = 0;
mp->mp_parent = 0;
return(OK); }

case MMSWAPON: {
struct mmswapon swapon;

if (mp->mp_effuid != SUPER_USER) return(EPERM);

if (sys_copy(who, D, (phys_bytes) ptr,
MM_PROC_NR, D, (phys_bytes) &swapon,
(phys_bytes) sizeof(swapon)) != OK) return(EFAULT);

return(swap_on(swapon.file, swapon.offset, swapon.size)); }

case MMSWAPOFF: {
if (mp->mp_effuid != SUPER_USER) return(EPERM);

return(swap_off()); }

default:
return(EINVAL);
}
}
For an MMSIGNON request, the mp_pid (servers do not have pid's), mp_procgrp (servers are in process group 0; process groups are used for signals), and mp_parent (servers do not have parents) fields of the process must be set to 0, any parents of the process must be released (i.e., the current process is the child of fork() and the parent of the process called wait()), and any children of the process must be adopted by init.


pause()


From pause(2):

"Pause never returns normally. It is used to give up control while waiting for a signal from kill(2) or the alarm timer, see alarm(2). Upon termination of a signal handler started during a pause, the pause call will return."

In other words, pause() returns after receiving a signal (e.g., a signal generated when the user presses 'Ctrl-\' or 'Ctrl-C').


0006167 
0006168          /* Our new identity as a server. */
0006169          this_proc = info.proc_nr;
The info struct was filled in by the kernel on line 6166.

info is of type systaskinfo, which is #define'd in include/sys/svrctl.h:

struct systaskinfo {
int proc_nr; /* Process number of caller. */
};


0006170 
0006171          /* Register the device group. */
0006172          device.dev= ip_dev;
read_conf() sets ip_dev to the value of the major device number of the device file "/dev/ip" (which is 7). This is very important: the /dev/ip file must exist before the service is started!

From inet(8):

"When inet is started, it will check and create all the necessary network devices before becoming a server. To know what major device number to use, it checks /dev/ip, so that device must already exist. It can be created by the utility MAKEDEV, if need be."


0006173          device.style= STYLE_CLONE;
The "style" of the device determines the function used to open and close file descriptors from the device. If the style is STYLE_CLONE, clone_opcl() (see do_svrctl() in src/fs/misc.c and clone_opcl() in src/fs/device.c) is called to handle open and close requests.


0006174          if (svrctl(FSSIGNON, (void *) &device) == -1) {
Register the network service with the file system (FS). This system call reports the major device number of "/dev/ip" (typically 7) to the file system so that all future messages making requests (e.g., open, read requests) involving /dev/ip and the other network-related files (e.g., /dev/udp, /dev/tcp) are sent to the network service. Also, as described above, this call also specifies that clone_opcl() is to be called for open and close requests.

do_svrctl() (defined in src/fs/misc.c) is the function that does all the work (as described above) for this system call:

/*===========================================================================*

* do_svrctl *
*===========================================================================*/
PUBLIC int do_svrctl()
{
switch (svrctl_req) {
case FSSIGNON: {
/* A server in user space calls in to manage a device. */
struct fssignon device;
int r, major;
struct dmap *dp;

if (fp->fp_effuid != SU_UID) return(EPERM);

r = sys_copy(who, D, (phys_bytes) svrctl_argp,
FS_PROC_NR, D, (phys_bytes) &device,
(phys_bytes) sizeof(device));
if (r != OK) return(r);

major= (device.dev >> MAJOR) & BYTE;
if (major >= max_major) return(ENODEV);
dp = &dmap[major];
if (dp->dmap_task != ANY) return(EBUSY);

switch (device.style) {
case STYLE_DEV: dp->dmap_opcl = gen_opcl; break;
case STYLE_TTY: dp->dmap_opcl = tty_opcl; break;
case STYLE_CLONE: dp->dmap_opcl = clone_opcl; break;
default: return(EINVAL);
}
dp->dmap_io = gen_io;
dp->dmap_task = who;
fp->fp_pid = PID_SERVER;
return(OK); }
default:
return(EINVAL);
}
}
The most interesting lines of do_svrctl() locate the appropriate entry of dmap[] (there is one entry in dmap[] for each device; dmap[] is described at the bottom of the sendrec() global comment), set the device's "open/close" function to clone_opcl(), and indicate which task is ultimately responsible for messages received for this device.


svrctl()


From svrctl(2):

Svrctl allows root to control the kernel in various ways or implements some very Minix specific system calls that don't deserve their own system call number.

This system call makes it easy to add new ways of setting and getting kernel parameters, but at the same time, backwards compatibility is not guaranteed. Read the include file to see what the struct's mentioned below look like. Most calls are root-only, unless specified otherwise.

The only way to know how to properly use these calls is to study the associated kernel or server code, or the programs that already use these calls.


Here is a description of the different requests made by the network service (from svrctl(2)):

MMSIGNON
Inform MM that the current process wants to become a server.

FSSIGNON
Register a new device with FS.

SYSSIGNON
Inform the kernel that the process want to become a server. The processes task number is filled-in in a struct systaskinfo.



In order to understand svrctl() better, it is helpful to look at some code. Here is the relevant code for the call from the network service that registers itself as a server to the memory manager. In other words, this is the relevant code for the svrctl(MMSIGNON, (void *) NULL) call that the network service makes:


From include/sys/svrctl.h:

#define MMSIGNON _IO ('M', 4)

#define _IO(x,y) ((x << 8) | y | _IOC_VOID)

#define _IOC_VOID 0x20000000


Therefore,

MMSIGNON = _IO ('M', 4) = ( (0x4D<<8) | 4 | 0x20000000 )
= 0x4D00 | 4 | 0x20000000
= 0x20004D04


From src/lib/other/_svrctl.c:

int svrctl(int request, void *argp)

{
message m;

m.m2_i1 = request;
m.m2_p1 = argp;

switch ((request >> 8) & 0xFF) {
case 'M':
case 'S':
/* MM handles calls for itself and the kernel. */
return _syscall(MM, SVRCTL, &m);
case 'F':
case 'I':
/* FS handles calls for itself and inet. */
return _syscall(FS, SVRCTL, &m);
default:
errno = EINVAL;
return -1;
}
}
(Note that MM is #define'd as 0 and FS is #define'd as 1 in include/lib.h.)


Since (0x20004D04 >> 8) & 0xFF = 0x4D = 'M', _syscall(MM, SVRCTL, &m) is called. _syscall(), in turn, calls _sendrec(). _sendrec() is not covered in this general comment but in its own general comment.

From src/lib/other/syscall.c:

PUBLIC int _syscall(who, syscallnr, msgptr)

int who;
int syscallnr;
register message *msgptr;
{
int status;

msgptr->m_type = syscallnr;
status = _sendrec(who, msgptr);
if (status != 0) {
/* 'sendrec' itself failed. */
/* XXX - strerror doesn't know all the codes */
msgptr->m_type = status;
}
if (msgptr->m_type < 0) {
errno = -msgptr->m_type;
return(-1);
}
return(msgptr->m_type);
}
Through the normal message passing system, a message is sent to the memory manager from the network service informing the memory manager of the network service's status as a server (in other words, the network service signs on to the memory manager as a server; a server has greater privileges than a normal process). This message is handled by the relevant function in call_vec[], an array of functions.

From src/mm/main.c:

/*===========================================================================*

* main *
*===========================================================================*/
PUBLIC void main()
{
/* Main routine of the memory manager. */

int result, proc_nr;
struct mproc *rmp;

mm_init(); /* initialize memory manager tables */

/* This is MM's main loop- get work and do it, forever and forever. */
while (TRUE) {
get_work(); /* wait for an MM system call */

/* If the call number is valid, perform the call. */
if ((unsigned) mm_call >= NCALLS) {
result = ENOSYS;
} else {
result = (*call_vec[mm_call])();
}

/* Send the results back to the user to indicate completion. */
if (result != E_NO_MESSAGE) setreply(who, result);

swap_in(); /* maybe a process can be swapped in? */

/* Send out all pending reply messages, including the answer to
* the call just made above. The processes must not be swapped out.
*/
for (proc_nr = 0, rmp = mproc; proc_nr < NR_PROCS; proc_nr++, rmp++) {
if ((rmp->mp_flags & (REPLY | ONSWAP)) == REPLY) {
if (send(proc_nr, &rmp->mp_reply) != OK)
panic("MM can't reply to", proc_nr);
rmp->mp_flags &= ~REPLY;
}
}
}
}


get_work() has two important tasks - to receive the incoming message and set the global variable mm_call to the message type.

/*===========================================================================*

* get_work *
*===========================================================================*/
PRIVATE void get_work()
{
/* Wait for the next message and extract useful information from it. */

if (receive(ANY, &mm_in) != OK) panic("MM receive error", NO_NUM);
who = mm_in.m_source; /* who sent the message */
mm_call = mm_in.m_type; /* system call number */

/* Process slot of caller. Misuse MM's own process slot for tasks (KSIG?). */
mp = &mproc[who < 0 ? MM_PROC_NR : who];
}
From src/mm/table.c:

_PROTOTYPE (int (*call_vec[NCALLS]), (void) ) = {

no_sys, /* 0 = unused */
do_mm_exit, /* 1 = exit */
do_fork, /* 2 = fork */
no_sys, /* 3 = read */
no_sys, /* 4 = write */
no_sys, /* 5 = open */
no_sys, /* 6 = close */
do_waitpid, /* 7 = wait */
no_sys, /* 8 = creat */
no_sys, /* 9 = link */
no_sys, /* 10 = unlink */
do_waitpid, /* 11 = waitpid */
no_sys, /* 12 = chdir */
no_sys, /* 13 = time */
no_sys, /* 14 = mknod */
no_sys, /* 15 = chmod */
no_sys, /* 16 = chown */
do_brk, /* 17 = break */
no_sys, /* 18 = stat */
no_sys, /* 19 = lseek */
do_getset, /* 20 = getpid */
no_sys, /* 21 = mount */
no_sys, /* 22 = umount */
do_getset, /* 23 = setuid */
do_getset, /* 24 = getuid */
no_sys, /* 25 = stime */
do_trace, /* 26 = ptrace */
do_alarm, /* 27 = alarm */
no_sys, /* 28 = fstat */
do_pause, /* 29 = pause */
no_sys, /* 30 = utime */
no_sys, /* 31 = (stty) */
no_sys, /* 32 = (gtty) */
no_sys, /* 33 = access */
no_sys, /* 34 = (nice) */
no_sys, /* 35 = (ftime) */
no_sys, /* 36 = sync */
do_kill, /* 37 = kill */
no_sys, /* 38 = rename */
no_sys, /* 39 = mkdir */
no_sys, /* 40 = rmdir */
no_sys, /* 41 = dup */
no_sys, /* 42 = pipe */
no_sys, /* 43 = times */
no_sys, /* 44 = (prof) */
no_sys, /* 45 = unused */
do_getset, /* 46 = setgid */
do_getset, /* 47 = getgid */
no_sys, /* 48 = (signal)*/
no_sys, /* 49 = unused */
no_sys, /* 50 = unused */
no_sys, /* 51 = (acct) */
no_sys, /* 52 = (phys) */
no_sys, /* 53 = (lock) */
no_sys, /* 54 = ioctl */
no_sys, /* 55 = fcntl */
no_sys, /* 56 = (mpx) */
no_sys, /* 57 = unused */
no_sys, /* 58 = unused */
do_exec, /* 59 = execve */
no_sys, /* 60 = umask */
no_sys, /* 61 = chroot */
do_getset, /* 62 = setsid */
do_getset, /* 63 = getpgrp */

do_ksig, /* 64 = KSIG: signals originating in the kernel */
no_sys, /* 65 = UNPAUSE */
no_sys, /* 66 = unused */
no_sys, /* 67 = REVIVE */
no_sys, /* 68 = TASK_REPLY */
no_sys, /* 69 = unused */
no_sys, /* 70 = unused */
do_sigaction, /* 71 = sigaction */
do_sigsuspend, /* 72 = sigsuspend */
do_sigpending, /* 73 = sigpending */
do_sigprocmask, /* 74 = sigprocmask */
do_sigreturn, /* 75 = sigreturn */
do_reboot, /* 76 = reboot */
do_svrctl, /* 77 = svrctl */
};
From src/mm/misc.c:

*=====================================================================*

* do_svrctl *
*=====================================================================*/
PUBLIC int do_svrctl()
{
int req;
vir_bytes ptr;

req = svrctl_req;
ptr = (vir_bytes) svrctl_argp;

/* Is the request for the kernel? */
if (((req >> 8) & 0xFF) == 'S') {
return(sys_sysctl(who, req, mp->mp_effuid == SUPER_USER, ptr));
}

switch(req) {
case MMSIGNON: {
/* A user process becomes a task. Simulate an exit by
* releasing a waiting parent and disinheriting children.
*/
struct mproc *rmp;
pid_t pidarg;

if (mp->mp_effuid != SUPER_USER) return(EPERM);

rmp = &mproc[mp->mp_parent];
tell_fs(EXIT, who, 0, 0);

pidarg = rmp->mp_wpid;
if ((rmp->mp_flags & WAITING) && (pidarg == -1
|| pidarg == mp->mp_pid || -pidarg == mp->mp_procgrp))
{
/* Wake up the parent. */
rmp->reply_res2 = 0;
setreply(mp->mp_parent, mp->mp_pid);
rmp->mp_flags &= ~WAITING;
}

/* Disinherit children. */
for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++) {
if (rmp->mp_flags & IN_USE && rmp->mp_parent == who) {
rmp->mp_parent = INIT_PROC_NR;
}
}

/* Become like MM and FS. */
mp->mp_pid = mp->mp_procgrp = 0;
mp->mp_parent = 0;
return(OK); }

case MMSWAPON: {
struct mmswapon swapon;

if (mp->mp_effuid != SUPER_USER) return(EPERM);

if (sys_copy(who, D, (phys_bytes) ptr,
MM_PROC_NR, D, (phys_bytes) &swapon,
(phys_bytes) sizeof(swapon)) != OK) return(EFAULT);

return(swap_on(swapon.file, swapon.offset, swapon.size)); }

case MMSWAPOFF: {
if (mp->mp_effuid != SUPER_USER) return(EPERM);

return(swap_off()); }

default:
return(EINVAL);
}
}
For an MMSIGNON request, the mp_pid (servers do not have pid's), mp_procgrp (servers are in process group 0; process groups are used for signals), and mp_parent (servers do not have parents) fields of the process must be set to 0, any parents of the process must be released (i.e., the current process is the child of fork() and the parent of the process called wait()), and any children of the process must be adopted by init.


0006175     printf("inet: error %d on registering ethernet devices\n",
0006176                            errno);
0006177                   pause();
pause()

From pause(2):

"Pause never returns normally. It is used to give up control while waiting for a signal from kill(2) or the alarm timer, see alarm(2). Upon termination of a signal handler started during a pause, the pause call will return."

In other words, pause() returns after receiving a signal (e.g., a signal generated when the user presses 'Ctrl-\' or 'Ctrl-C').


0006178          }
0006179 
0006180 #ifdef BUF_CONSISTENCY_CHECK
0006181          inet_buf_debug= 100;
0006182          if (inet_buf_debug)
0006183          {
0006184                   ip_warning(( "buffer consistency check enabled" ));
0006185          }
0006186 #endif
0006187 
0006188          if (sys_findproc("SYN_AL", &synal_tasknr, 0) != OK)
sys_findproc() maps a task name to a task number and is implemented in lib/syslib/sys_findproc.c.

sys_findproc() is used here to find the task number of the Asynchronous Alarm Task. synal_tasknr is a global variable (defined on line 6073) that is set to this task number and is used to determine if a received packet is from the Asynchronous Alarm Task (see line 6132).

When a network service wishes to set the timer for an operation, set_timer() is called.


synchronous alarm task


The network service listens for messages from 3 sources: the file system service, the synchronous alarm task, and the ethernet task. The function of the synchronous alarm task is described in Operating Systems, Design and Implementation by Tanenbaum and Woodhull, page 229:

"The synchronous alarm mechanism was added to MINIX to support the network server, which like the memory manager and the file server, runs as a separate process. Frequently there is a need to set a limit on the time a process may be blocked while waiting for input. For instance in a network, failure to receive an acknowledgement of a data packet within a definite period is probably due to a failure of transmission. A network server can set a synchronous alarm before it tries to receive a message and blocks. Since the synchronous alarm is delivered as a message, it will unblock the server eventually if no message is received from the network. Upon receiving any message the server must first reset the alarm. Then by examining the type or origin of the message, it can determine a packet has arrived or if it has been unblocked by a timeout. If it is the latter, then the server can try to recover, usually by resending the last unacknowledged packet."

In other words, the synchronous alarm task prevents client connections (specifically, tcp and arp connections) from hanging.

The synchronous alarm works as follows:

If a client wishes to set a synchronous alarm, clck_timer() is called and a timer is added to the existing timers. For example, the arp client calls clck_timer() in order to set a limit on the time it's willing to wait for another system on the network to respond to an arp request. If the new timer expires before any of the other timers are due to expire, a message is sent to the clock task requesting an alarm set for the appropriate time. (Note that the clock task is only aware of at most one alarm at any given time.)

When the alarm message is eventually received by the network service (this process), set_timer() sets the global variable clck_call_expire and returns. The next time around its endless while loop, main() processes the received alarm message by calling clck_expire_timers(). This function calls the client-specific function (e.g., arp_timeout()) that handles alarms, passing in the necessary information so that the client can identify the connection associated with the alarm.

After handling the alarm, set_timer() again sends a message to the clock task requesting an alarm message be sent for the next timer due to expire (assuming that there are other timers in timer_chain).


0006189     ip_panic(( "unable to find synchronous alarm task\n"));
ip_panic()

ip_panic() (for the case where the system is not "CRAMPED") is #define'd as follows:

#define ip_panic(print_list) (panic0(this_file, __LINE__), printf print_list, panic())

panic0() simply prints out an error message indicating where the panic was called and panic() does a stacktrace before calling sys_abort() to bring the system down.


0006190 
0006191          mq_init();
mq_init()

mq_init() initializes mq_list[] and mq_freelist as follows:



Click here for a detailed description of mq_list[] and mq_freelist.


0006192          bf_init();
bf_init()

bf_init() is called by nw_init(). bf_init() allocates space for buffers512[] and accessors[] and initializes buf512_freelist and acc_freelist.


0006193          clck_init();
clck_init()

clck_init() is called during the initialization of the network service to initialize various clock related global variables (e.g., clck_call_expire).


0006194          sr_init();
Notice that sr_init() precedes the protocol-specific initiailization functions (e.g., eth_init(), arp_init()). These protocol-specific initialization functions access and modify sr_fd_table[] and sr_fd_table[] is initialized by sr_init().


sr_init()


sr_init() initializes sr_fd_table[] and repl_queue.


0006195          eth_init();
eth_init()

eth_init() initializes the ethernet file descriptors and then the operating system-independent fields of the ethernet ports before calling osdep_eth_init() to initialize the operating system-dependent fields of the ethernet ports.


0006196 #if ENABLE_ARP
ENABLE_ARP, ENABLE_IP, ENABLE_PSIP, etc are all #define'd in inet/config.h and are set by default.


0006197          arp_init();
arp_init()

arp_init() sets the ap_state field of all the arp ports to APS_ERROR. Later, when the ip code acquires an arp port, the ap_state field of the acquired arp port is set to APS_INITIAL.


0006198 #endif
0006199 #if ENABLE_PSIP
The psip (pseudo IP) functions and files are not covered in this documentation.


0006200          psip_init();
0006201 #endif
0006202 #if ENABLE_IP
0006203          ip_init();
ip_init()

ip_init() initializes ip_ass_table[] (the fragmentation table), ip_fd_table[] (the ip file descriptor table), and ip_port_table[] (the ip port table).

ip_init() also calls icmp_init() (icmp initialization) and ipr_init() (router initialization).

ip_init() is called a single time during the initialization of the network service. ip_init() is called by nw_init().


0006204 #endif
0006205 #if ENABLE_TCP
The tcp code is not covered in this documentation.


0006206          tcp_init();
0006207 #endif
0006208 #if ENABLE_UDP
0006209          udp_init();
udp_init()

Like udp_prep(), udp_init() is called only once. udp_init() initializes udp_port_table[] and calls sr_add_minor() and udp_main() to complete the initialization of the udp layer.


0006210 #endif
0006211 }
0006212 
0006213 #if !CRAMPED
CRAMPED

CRAMPED is #define'd in inet.h as 0 for an 80836 processor or better and 1 for anything less (e.g., 8086). In the figures and discussions in this documentation, it is assumed that the system is an 80836 or better.

If the system is not even an 80386, it is a fair assumption that there is not much memory to work with (i.e., the memory is "cramped").ip_panic()

ip_panic() (for the case where the system is not "CRAMPED") is #define'd as follows:

#define ip_panic(print_list) (panic0(this_file, __LINE__), printf print_list, panic())

panic0() simply prints out an error message indicating where the panic was called and panic() does a stacktrace before calling sys_abort() to bring the system down.


0006214 PUBLIC void panic0(file, line)
0006215 char *file;
0006216 int line;
0006217 {
0006218          printf("panic at %s, %d: ", file, line);
0006219 }
0006220 
0006221 PUBLIC void panic()
0006222 {
0006223   printf("\ninet stacktrace: ");
0006224          stacktrace();
stacktrace()

stacktrace() is a primitive debugger which is called by panic(). stacktrace() returns the addresses of the functions that were called prior to the call to panic(). stacktrace() can be used in combination with the nm utility (which displays the symbol table of the executable; the symbol table includes address to function name mappings) and a disassembler (the disassembler is needed to determine the exact instruction corresponding to each function call; nm can only be used to determine the names of the functions).

Below is example output of panic(). The values after the string "inet stacktrace" are the addresses of the functions called prior to the call to panic().

-----
panic at mnx_eth.c, 44: unable to find task RTL8139: -3

inet stacktrace 0x29a 0xa02 0x547d 0x254 0x61 0x49
Hit ESC to reboot, F-keys for debug dumps
-----


0006225          sys_abort(RBT_PANIC);
0006226 }
0006227 
0006228 #else /* CRAMPED */
0006229 
0006230 PUBLIC void panic(file, line)
0006231 char *file;
0006232 int line;
0006233 {
0006234   printf("panic at %s, %d\n", file, line);
0006235          sys_abort(RBT_PANIC);
0006236 }
0006237 #endif
0006238 
0006239 #if !NDEBUG
0006240 PUBLIC void bad_assertion(file, line, what)
bad_assertion() is used in the assert macro. bad_assertion() and bad_compare() do essentially what ip_panic() does; they all print out some debugging information and then bring the system down.


0006241 char *file;
0006242 int line;
0006243 char *what;
0006244 {
0006245          panic0(file, line);
0006246   printf("assertion \"%s\" failed", what);
0006247          panic();
0006248 }
0006249 
0006250 
0006251 PUBLIC void bad_compare(file, line, lhs, what, rhs)
0006252 char *file;
0006253 int line;
0006254 int lhs;
0006255 char *what;
0006256 int rhs;
bad_compare() is used only in the compare macro. bad_compare() logs some information and then calls panic() (defined above).


compare()


compare is #define'd in inet/generic/assert.h:

#define compare(a,t,b) (!((a) t (b)) ? bad_compare(this_file, __LINE__, \
(a), #a " " #t " " #b, (b)) : (void) 0)

and bad_compare() is defined in inet/inet.c.

If the relationship between the 3 arguments in compare() does not hold, some debugging output is emitted and then Minix is terminated.

For example, if compare(result, >=, 0) is called and result (the first argument) is -1, Minix will be terminated.


0006257 {
0006258          panic0(file, line);
0006259          printf("compare (%d) %s (%d) failed", lhs, what, rhs);
0006260          panic();
0006261 }
0006262 #endif /* !NDEBUG */
0006263 
0006264 /*
0006265  * $PchId: inet.c,v 1.12 1996/12/17 07:58:19 philip Exp $
0006266  */