Autor Thema: IPC-Konzept  (Gelesen 6392 mal)

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« am: 05. February 2010, 19:03 »
Hallo,


ich möchte Euch mal mein (vorläufiges) IPC-Konzept vorstellen und hoffe auf zahlreiche Meinungen bzw. Verbesserungsvorschläge (oder gerne auch Lobeshymnen :-D ).


Ich möchte synchrone und asynchrone Messages unterstützen.
Die synchronen gehen direkt an ein bestimmtes Empfänger-Objekt und liefern immer eine eindeutig zugehörige Antwort zurück, also ein zusammenhängendes Frage/Antwort-Spiel wo der fragende Thread blockiert bis seine Antwort eintrifft.
Die asynchronen gehen entweder auch an ein konkretes Empfänger-Objekt oder an einen bestimmten Prozess und liefern keine Antwort, der Sender wird nicht blockiert sondern kann sofort weiterarbeiten falls er das kopieren der Message-Daten gewünscht hat oder er muss warten bis der Empfänger die Daten freigibt.
Für die meisten Anwendungsfälle, vor allem für RPC, werden wohl die synchronen Messages benutzt. Die asynchronen Messages dienen eher System-Nachrichten/Signale vom Kernel an Prozesse und IRQ-Messages.
Die Message-Verarbeitung erfolgt in PopUp-Threads welche vom OS-Kernel automagisch, im Prozess des betreffenden Service, erstellt werden und als Einsprungspunkt die zugewiesene Message-Handler-Funktion nutzen (vielen Dank an taljeth für diese grandiose Idee, welche wohl noch nie jemand benutzt hat). Es können auch mehrere Threads pro Message-Empfänger-Objekt gleichzeitig aktiv sein so das z.B. das TCP-Modul von mehreren Prozessen gleichzeitig benutzt werden kann. Diese PopUp-Threads können ganz normal unterbrochen werden und auch alle üblichen Synchronisations-Mechanismen benutzen (auch im Zusammenspiel mit den normalen Threads im Service-Prozess).

Die IPC-SYSCALL-Schnittstelle ist folgende:/**
 * Function-Prototyp for Message-Handler-Function for synchrounus Messages
 * called directly in a Kernel generatet Popup-Thread (need the exact Calling-Conventions)
 *  - Handler-Parameter (from Message-Target-Creater, the pointed Data must not modified)
 *  - Sender-ID (Process-ID if from an other Process or Thread-ID if from current Process)
 *  - Question-1 (the pointed Data must not modified)
 *  - Question-1-Size
 *  - Question-2 (the pointed Data must not modified)
 *  - Question-2-Size
 *  - Answer-1
 *  - Answer-1-Maximum-Size
 *  - Answer-2
 *  - Answer-2-Maximum-Size
 *  the Message-Handler can not simply return, it must call 'syscall_msg_sync_handler_end()' to give the Controll back to the Kernel, the Link-Register is set to -2 by kernel and the Stack is clear
 */
typedef void msg_sync_func(const void* const param,const long sender_id,const void* const question1,const ulong question1_length,const void* const question2,const ulong question2_length,void* const answer1,const ulong answer1_max_length,void* const answer2,const ulong answer2_max_length);

/**
 * Function-Prototyp for Message-Handler-Function for asynchrounus Messages
 * called directly in a Kernel generatet Popup-Thread (need the exact Calling-Conventions)
 *  - Handler-Parameter (from Message-Target-Creater, the pointed Data must not modified)
 *  - Sender-ID (Process-ID if from an other Process or Thread-ID if from current Process)
 *  - Message-1 (the pointed Data must not modified)
 *  - Message-1-Size
 *  - Message-2 (the pointed Data must not modified)
 *  - Message-2-Size
 *  the Message-Handler can not simply return, it must call 'syscall_msg_asyn_handler_end()' to give the Controll back to the Kernel, the Link-Register is set to -2 by kernel and the Stack is clear
 */
typedef void msg_asyn_func(const void* const param,const long sender_id,const void* const message1,const ulong message1_length,const void* const message2,const ulong message2_length);

/**
 * create a Message-Target for synchrounus Messages
 *  - Handler-Function for the Message-Popup-Thread
 *  - Parameter for the Handler (the pointed Data must not modified)
 *  - maximum allowed Question/Answer-1-Size (4kBytes...Kernel-Depended-Maximum)
 *  - maximum allowed Question/Answer-2-Size (0 or 4kBytes...Kernel-Depended-Maximum)
 *  - maximum allowed Popup-Threads that runs simultaneously for this Message-Target (1...Kernel-Depended-Maximum)
 *  - start Stack-Size for the Popup-Thread (Message-Sizes are not included), this is not a real Limit, stack can ever grow up to an OS-Depended Maximum-Limit (0 means an Kernel-spezific Default-Value)
 *  - Priority for the Popup-Thread : offset to the Process-Base-Priority (if the Priority of the calling Thread is not inherited)
 *  returns the Message-Target-ID or Error-Code
 */
long syscall_msg_sync_create(msg_sync_func* const func,const void* const param,const ulong message1_max_length,const ulong message2_max_length,const uint max_threads = 1,const ulong stack_size = 0,const int priority = 0);

/**
 * create a Message-Target for asynchrounus Messages
 *  - Handler-Function for the Message-Popup-Thread
 *  - Parameter for the Handler (the pointed Data must not modified)
 *  - maximum allowed Message-1-Size (4kBytes...Kernel-Depended-Maximum)
 *  - maximum allowed Message-2-Size (0 or 4kBytes...Kernel-Depended-Maximum)
 *  - maximum allowed Popup-Threads that runs simultaneously for this Message-Target (1...Kernel-Depended-Maximum)
 *  - start Stack-Size for the Popup-Thread (Message-Sizes are not included), this is not a real Limit, stack can ever grow up to an OS-Depended Maximum-Limit (0 means an Kernel-spezific Default-Value)
 *  - Priority for the Popup-Thread : offset to the Process-Base-Priority
 *  returns the Message-Target-ID or Error-Code
 */
long syscall_msg_asyn_create(msg_asyn_func* const func,const void* const param,const ulong message1_max_length,const ulong message2_max_length,const uint max_threads = 1,const ulong stack_size = 0,const int priority = 0);

/**
 * create (open) the Process-Message-Target for asynchrounus System-Messages to the current Process
 *  ever asynchrounus : simple Messages
 *  ever maximum 64kBytes per Message-1
 *  ever 0Bytes per Message-2 (no additional Data)
 *  ever only one Popup-Thread
 *  - Handler-Function for the Message-Popup-Thread
 *  - Parameter for the Handler (the pointed Data must not modified)
 *  - start Stack-Size for the Popup-Thread (Message-Sizes are not included), this is not a real Limit, stack can ever grow up to an OS-Depended Maximum-Limit (0 means an Kernel-spezific Default-Value)
 *  - Priority for the Popup-Thread : offset to the Process-Base-Priority
 *  returns the Message-Target-ID or Error-Code
 */
long syscall_msg_proc_create(msg_asyn_func* const func,const void* const param,const ulong stack_size = 0,const int priority = 0);

/**
 * create a Message-Target for asynchrounus IRQ-Messages
 *  for receiving IRQ-Messages spezial Privileges are needed
 *  ever asynchrounus : simple Messages
 *  ever maximum 32Bytes per Message-1 (contains a simple Struct with Infos about the IRQ, ever copyed ontu Popup-Thread-Stack)
 *  ever 0Bytes per Message-2 (no additional Data)
 *  - Handler-Function for the Message-Popup-Thread
 *  - Parameter for the Handler (the pointed Data must not modified)
 *  - IRQ-Number
 *  - maximum allowed Popup-Threads that runs simultaneously for this Message-Target (1...Kernel-Depended-Maximum , the Kernel can fix this to one Thread)
 *  - start Stack-Size for the Popup-Thread (Message-Sizes are not included), this is not a real Limit, stack can ever grow up to an OS-Depended Maximum-Limit (0 means an Kernel-spezific Default-Value)
 *  - Priority for the Popup-Thread : offset to the Process-Base-Priority
 *  returns the Message-Target-ID or Error-Code
 */
long syscall_msg_irq_create(msg_asyn_func* const func,const void* const param,const uint irq_num,const uint max_threads = 1,const ulong stack_size = 0,const int priority = 0);

/**
 * kill a Message-Target for synchrounus Messages
 *  - Message-Target-ID
 *  - correct Handler-Function as Access-Secret
 *  - correct Parameter as Access-Secret (the pointed Data can modified after the return of this SYSCALL)
 *  returns Success/Error-Code
 */
long syscall_msg_sync_kill(const long msg_id,msg_sync_func* const func,const void* const param);

/**
 * kill a Message-Target for asynchrounus Messages (or System-Messages or IRQ-Messages)
 *  - Message-Target-ID (or Process-ID)
 *  - correct Handler-Function as Access-Secret
 *  - correct Parameter as Access-Secret (the pointed Data can modified after the return of this SYSCALL)
 *  returns Success/Error-Code
 */
long syscall_msg_asyn_kill(const long msg_id,msg_asyn_func* const func,const void* const param);

/**
 * End of a Message-Handler for synchrounus Messages
 * this SYSCALL can only called from a Message-Handler (inside of the Popup-Thread)
 *  - real Answer-1-Size (must be smaller or equal to the maximum Answer-1-Size)
 *  - real Answer-2-Size (must be smaller or equal to the maximum Answer-2-Size)
 *  do not return (the Popup-Thread is killed/recycled by the Kernel)
 */
void syscall_msg_sync_handler_end(const ulong answer1_length,const ulong answer2_length);

/**
 * End of a Message-Handler for asynchrounus Messages
 * this SYSCALL can only called from a Message-Handler (inside of the Popup-Thread)
 *  do not return (the Popup-Thread is killed/recycled by the Kernel)
 */
void syscall_msg_asyn_handler_end();

/**
 * free the Message-Memorys for asynchrounus Messages (for copyed messages it has no effect)
 * this SYSCALL can only called from a Message-Handler (inside of the Popup-Thread)
 * the Message-Pointers are invalid after this SYSCALL
 */
void syscall_msg_asyn_handler_free_msg();

/**
 * send a synchrounus Question and wait for its Answer
 *  - Message-Target-ID
 *  - Question-1 (tx) (the pointed Data must not modified while this SYSCALL)
 *  - Question-1-Size (tx)
 *  - Question-2 (tx) (the pointed Data must not modified while this SYSCALL)
 *  - Question-2-Size (tx)
 *  - Answer-1 (rc) (the pointed Data must not modified while this SYSCALL)
 *  - Answer-1-Size (rc) (the pointed Data must not modified while this SYSCALL)
 *  - Answer-1-Maximum-Size
 *  - Answer-2 (rc) (the pointed Data must not modified while this SYSCALL)
 *  - Answer-2-Size (rc) (the pointed Data must not modified while this SYSCALL)
 *  - Answer-2-Maximum-Size
 *  - inherit the absolute Priority of the calling Thread to the PopUp-Thread
 *  - do not share the Question/Answer-Memorys, Kernel must copy if true, small Messages are ever copyed
 *  returns Success/Error-Code
 */
long syscall_msg_sync_send(const long msg_id,const void* const question1,const ulong question1_length,const void* const question2,const ulong question2_length,void* const answer1,ulong* const answer1_length,const ulong answer1_max_length,void* const answer2,ulong* const answer2_length,const ulong answer2_max_length,const bool prio_inherit = true,const bool no_sharing = false);

/**
 * send an asynchrounus Message and do not obligatory wait (typical the current Thread is shedulled and the new Popup-Thread becomes the current CPU-Thread, if a new Popup-Thread is possible)
 *  - Message-Target-ID
 *  - Message-1 (tx) (the pointed Data must not modified while this SYSCALL)
 *  - Message-1-Size (tx)
 *  - Message-2 (tx) (the pointed Data must not modified while this SYSCALL)
 *  - Message-2-Size (tx)
 *  - do not share the Message-Memorys, Kernel must copy if true, small Messages are ever copyed
 *  - wait for the real finish of the Message-Handler (independed of the Sharing-State of the Message-Memorys)
 *  returns Success/Error-Code
 */
long syscall_msg_asyn_send(const long msg_id,const void* const message1,const ulong message1_length,const void* const message2,const ulong message2_length,const bool no_sharing = false,const bool wait = false);

/**
 * get Infos about a synchrounus Message-Target
 *  - Message-Target-ID
 *  - count of current working Threads
 *  - start Stack-Size for PopUp-Thread
 *  - Handler-Function-Pointer
 *  - Handler-Parameter-Pointer
 *  returns Success/Error-Code
 */
long syscall_msg_sync_info(const long msg_id,ulong* const thread_count,ulong* const thread_stack_size,const msg_sync_func** const func,const void** const param);

/**
 * get Infos about an asynchrounus Message-Target (or the Process-Message-Target)
 *  - Message-Target-ID or Process-ID
 *  - count of current working Threads
 *  - start Stack-Size for PopUp-Thread
 *  - Handler-Function-Pointer
 *  - Handler-Parameter-Pointer
 *  returns Success/Error-Code
 */
long syscall_msg_asyn_info(const long msg_id,ulong* const thread_count,ulong* const thread_stack_size,const msg_asyn_func** const func,const void** const param);
auch wenn es nicht so aussieht aber alle Parameter werden komplett in Registern übergeben und die Kommentare sind noch nicht im Doxygen-Style. 13 SYSCALLs nur für IPC sind zwar recht viel aber ich denke damit lassen sich alle nur erdenklichen Anwendungsfälle abdecken.
Wie man sieht möchte ich immer 2 zusammengehörige Message-Daten-Blöcke benutzen so das RPC-Kommando und RPC-Nutzdaten getrennt voneinander übergeben werden können, es müssen aber nicht immer beide benutzt werden.
Die Message-Daten-Blöcke sollen nicht kopiert werden sondern der OS-Kernel soll in der LDT des Message-Empfängers (Service) ein neues Segment anlegen das genau auf den linearen Speicherbereich vom Message-Sender (Client) zeigt, da das nicht auf beliebige Bytegenauigkeit sondern nur mit 256Byte-Alignment geht sieht der Service maximal 2 * 255 Bytes pro geteiltem Speicherbereich aus dem privaten Speicher des Client-Prozess was sicher verschmerzbar ist (wenn der Client das nicht will muss er das kopieren anfordern oder seine Daten passend ausrichten bzw. Platz lassen). Am Ende des PopUp-Threads werden die Alias-Segmente wieder aus dem Kontext des Service gelöscht.
Die Pointer welche der Service bekommt sind nicht die selben wie jene die der Client benutzt, der Kernel muss hier passend umsetzen. In den übermittelten Daten können natürlich auch keine Pointer benutzt werden (so wie bei Shared-Memory üblich).

Ein kleines Beispiel:
Ein Programm will Daten (100000Bytes) über eine Serielle Schnittstelle senden.lib_function_ser_tx(ser_dev_handle,&data_array,100000);in der Application-Library gibts dann dasextern int lib_function_ser_tx(const int ser_dev_handle,const void* const data,const ulong data_length)
{
  byte cmd[??];
  //'cmd' mit nem Kommando befüllen, in Abhängigkeit von 'ser_dev_handle'
  byte ans[??]; ulong ans_length=0;
  const long retval = syscall_msg_sync_send(ser_handler_msg_id,&cmd,sizeof(cmd),data,data_length,&ans,&ans_length,sizeof(ans),NULL,NULL,0);
  //check 'retval' und 'ans' ob alles okay ist
  return 0; //all okay
}
der Treiber für die Serielle Schnittstelle sieht dann etwa so ausvoid ser_handler_msg_func(const void* const param,const long sender_id,const void* const question1,const ulong question1_length,const void* const question2,const ulong question2_length,void* const answer1,const ulong answer1_max_length,void* const answer2,const ulong answer2_max_length)
{
  //check Kommando :
  switch(question1)
   {
     ???:
       break;

     ???:
       break;

     senden:
       {
       //für die Nutzdaten 'question2' die physikalische Adresse ermitteln, sind eventuell mehrere Pages
       //dem BUS-Master-fähigem UART diese Bereiche ans Ende seiner TX-Queue anhängen, im letzten Abschnitt das "Interrupt wenn alles gesendet"-Flag setzen
       //PopUp-Thread schlafen legen bis vom IRQ-Handler wieder aufgeweckt wird, der UART holt die Daten selbstständig aus dem RAM und sendet diese ohne jegliches zutun der CPU
       //Antwort 'answer1' passend befüllen
       syscall_msg_sync_handler_end(???/*Antwortgröße*/,0); //kommt nicht zurück
       }
       break;
   }
}
Das schöne an diesem Szenario ist das die Daten niemals kopiert werden müssen. Das lässt sich auch problemlos auf mehr Ebenen ausdehnen z.B. auf Datei-Zugriffe. Da könnte der VFS-Service das Kommando entgegennehmen und der PopUp-Thread called dann seinerseits den Service für das entsprechende Dateisystem welcher dann wieder den Block-Device-Treiber per RPC aufruft, auch hier müssen die Userdaten nicht unbedingt kopiert werden so das der (S)ATA-Controller die eigentlichen Datei-Daten direkt in/aus dem Speicher der Zielanwendung schreibt/ließt. Das alles kostet nur etwas Speicher für die Stacks der schlafenden PopUp-Threads welche selber RPC benutzen.


Ich währe sehr dankbar wenn Ihr da mal drüber schaut ob euch noch offensichtliche Fehler auffallen.

Das ganze sollte sich identisch für Flat-Memory umsetzen lassen, also wenn jemand mir seine praktischen Erfahrungen mit diesem Konzept mitteilen möchte dann nur zu. :wink:


Grüße
Erik
« Letzte Änderung: 05. February 2010, 20:03 von erik.vikinger »
Reality is that which, when you stop believing in it, doesn't go away.

 

Einloggen