Comunicarea prin mailbox este cea mai simpla metoda de a trimite date de dimensiuni mici intre SPU si PPU sau intre SPU-uri. Aceasta tehnica este prezentata mai pe larg in laboratorul 7.
Exista doua biblioteci principale folosite pentru acest lucru: pentru cod PPU - libspe2.h
(descrisa in SPE Runtime Management Library), iar pentru cod SPU - spu_mfcio.h
(descrisa in C/C++ Language Extensions for Cell BE Architecture).
De notat ca prefixul functiilor de lucru cu mailbox-uri este diferit in cele doua biblioteci:
* functiile pentru cod PPU au denumiri de forma spe_*
(e.g. spe_out_mbox_read
)
* functiile pentru cod SPU au denumiri de forma spu_*
(e.g. spu_write_out_mbox
)
Aspectul cel mai important legat de comunicare este caracterul blocant / non-blocant. Varianta non-blocanta presupune ca programul sa verifice constant aparitia unui mesaj. In lipsa unor prelucrari utile intre 2 verificari, acest comportament reprezinta busy-waiting si este ineficient, irosind resurse de procesare / energie. Varianta blocanta presupune asteptarea efectuarii operatiei, fiind mai eficienta decat verificarea starii mailbox-ului intr-o bucla stransa. Cea mai eficienta varianta se bazeaza pe mecanismul de evenimente (asemanator mecanismului select invatat la Protocoale de Comunicatii).
In cele ce urmeaza vom incepe prin a construi un mecanism rudimentar de comunicare intre PPU si SPU. Prin exemplele urmatoare vom acoperi urmatoarele modele de comunicare:
Trebuie mentionat ca acest mecanism nu este unul foarte elegant si ne va servi la trimiterea parametrilor pentru inceput, in exemplele mai avansate fiind inlocuit de trimiterea parametrilor prin DMA si mailbox sau prin alte metode de comunicare.
In acest exemplu dorim sa stim pe care SPU ne aflam cand rulam. Ne vom folosi de cei doi parametrii aditionali din functia main
, de tipul unsigned long long
(ull este tipul de date cel mai mare posibil).
In codul PPU, parametrii sunt: spe
, argp
si envp
. Echivalentul lor in codul SPU, pe functia main
sunt respectiv speid
, argp
si envp
.
PPU | SPU |
---|---|
#include <libspe2.h> int spe_context_run(spe_context_ptr_t spe, -- unsigned int* entry, unsigned int runflags, void* argp, -- void* envp, -- spe_stop_info_t *stopinfo) | -> int main(unsigned long long speid, -> unsigned long long argp, -> unsigned long long envp) |
Pentru a putea trimite toti parametrii necesari prin functia pthread_create
(care primeste ca parametrii o functie de tipul void* myfunc(void* arg)
si un singur parametru de tip void*
) vom defini o structura ce ii va incapsula pe toti:
typedef struct { long cellno; spe_context_ptr_t spe; } thread_arg_t;
Structura va fi populata pentru fiecare SPE si trimisa ca parametru (dupa cast la void*
) cu ajutorul vectorului arg
:
int main(void) { int i; spe_context_ptr_t ctxs[SPU_THREADS]; pthread_t threads[SPU_THREADS]; thread_arg_t arg[SPU_THREADS]; //... ctxs[i] = spe_context_create(0, NULL); //... arg[i].cellno = i; arg[i].spe = ctxs[i]; pthread_create(&threads[i], NULL, &ppu_pthread_function, (void*)&arg[i])); //... }
In interiorul functiei ppu_pthread_function
se face cast la loc in thread_arg_t
:
void* ppu_pthread_function(void* thread_arg) { thread_arg_t* arg = (thread_arg_t*)thread_arg; unsigned int entry = SPE_DEFAULT_ENTRY; //... spe_context_run(arg->spe, &entry, 0, (void*)arg->cellno, NULL, NULL); //... }
In codul SPU vom primi valoarea in argp
si va fi nevoie de cast la tipul original (long
in acest caz):
#include <stdio.h> int main(unsigned long long speid, unsigned long long argp, unsigned long long envp) { //... printf("[SPU %ld] is up.\n", (long)argp); return 0; }
Arhiva completa se poate descarca de aici.
Pentru a implementa aceasta metoda, trebuie sa folosim o functie care trimite mesaje de pe PPU: spe_in_mbox_write
si una care citeste datele pe SPU: spu_read_in_mbox
. Folosim varianta blocanta a metodei de trimitere a datelor (parametrul al patrulea al spe_in_mbox_write
este setat pe SPE_MBOX_ALL_BLOCKING
), dar din moment ce trimitem un singur mesaj fiecarui SPU, suntem siguri ca aceasta nu se va bloca.
Nu uitati sa consultati manualul de referinta in cazul fiecarei functii:
* pentru cod PPU - libspe2.h
: SPE Runtime Management Library, cap 7, pp. 69-77
* pentru cod SPU - spu_mfcio.h
: C/C++ Language Extensions for Cell BE Architecture, cap. 4.13, pp. 101-102
In continuare prezentam codul schelet pentru un singur SPU. La sfarsitul sectiunii puteti descarca un cod complet pentru toate cele 16 SPU-uri.
PPU | SPU |
---|---|
#include <stdio.h> #include <libspe2.h> #include <pthread.h> extern spe_program_handle_t spu_mailbox; int main(void) { unsigned int mbox_data = 55; spe_context_ptr_t speid; unsigned int entry = SPE_DEFAULT_ENTRY; speid = spe_context_create(0, NULL); spe_program_load(speid, &spu_mailbox); // se poate scrie in mailbox inainte de // pornirea programului SPU; // trimitem un singur mesaj unui mailbox // gol, astfel apelul nu se va bloca // niciodata si nu putem avea deadlock spe_in_mbox_write(speid, &mbox_data, 1, SPE_MBOX_ALL_BLOCKING); spe_context_run(speid, &entry, 0, (void*)0, 0, NULL); // nu putem scrie in mailbox in acest punct // deoarece spe_context_run este blocanta, // asteptand terminarea programului SPU, // care la randul sau asteapta trimiterea // mesajului prin mailbox => deadlock //spe_in_mbox_write(speid, &mbox_data, 1, // SPE_MBOX_ALL_BLOCKING); printf("[PPU] data sent to SPU 0 = %d\n", mbox_data); spe_context_destroy(speid); return 0; } | #include <stdio.h> #include <spu_mfcio.h> int main(unsigned long long speid, unsigned long long argp, unsigned long long envp) { (void)speid; (void)envp; unsigned int mbox_data; // citeste mesajul mbox_data = spu_read_in_mbox(); printf("[SPU %ld] received data = %d.\n", (long)argp, (int)mbox_data); return 0; } |
Arhiva completa se poate descarca de aici.
Pentru a implementa aceasta metoda trebuie folosita o functie care trimite de pe SPU: spu_write_out_intr_mbox
si una care citeste datele pe PPU spe_out_intr_mbox_read
. Folosim varianta out_intr a mailbox-ului pentru a putea astepta mesaje pe PPU in mod blocant (fara busy-waiting).
Nu uitati sa consultati manualul de referinta in cazul fiecarei functii:
libspe2.h
: SPE Runtime Management Library, cap 7, pp. 69-77spu_mfcio.h
: C/C++ Language Extensions for Cell BE Architecture, cap. 4.13, pp. 101-102In continuare prezentam codul schelet pentru un singur SPU. La sfarsitul sectiunii puteti descarca un cod complet pentru toate cele 16 SPU-uri.
PPU | SPU |
---|---|
#include <stdio.h> #include <libspe2.h> #include <pthread.h> extern spe_program_handle_t spu_mailbox; int main(void) { unsigned int mbox_data; spe_context_ptr_t speid; unsigned int entry = SPE_DEFAULT_ENTRY; speid = spe_context_create(0, NULL); spe_program_load(speid, &spu_mailbox); spe_context_run(speid, &entry, 0, (void*)0, (void*)55, NULL); // putem citi ultimele valori ramase // in mailbox, dupa terminarea // programului SPU spe_out_intr_mbox_read(speid, &mbox_data, 1, SPE_MBOX_ALL_BLOCKING); printf("[PPU] SPU 0 sent data = %d\n", (int)mbox_data); spe_context_destroy(speid); return 0; } | #include <stdio.h> #include <spu_mfcio.h> int main(unsigned long long speid, unsigned long long argp, unsigned long long envp) { (void)speid; // trimitem o singura valoare unui mailbox // gol, astfel apelul nu se va bloca // niciodata si nu putem avea deadlock printf("[SPU %ld] sending data = %d ...\n", (long)argp, (int)envp); spu_write_out_intr_mbox((unsigned int)envp); return 0; } |
Arhiva completa se poate descarca de aici.
Ideea in folosirea evenimentelor este eficientizarea procesarii in codul PPU a diferitelor semnale de la SPU-uri, folosind un singur thread. Interfata este asemanatoare cu apelul de sistem select
, multiplexand diferite evenimente din multiple surse. In privinta codului SPU scrierea in mailbox se face la fel. Detalii despre functiile folosite in lucrul cu evenimente gasiti in SPE Runtime Management Library, cap. 6, pp. 41-48.
Un schelet de cod, care foloseste un singur SPU, este prezentat mai jos.
PPU | SPU |
---|---|
#include <stdio.h> #include <libspe2.h> #include <pthread.h> extern spe_program_handle_t spu_mailbox; int main(void) { unsigned int mbox_data; spe_event_handler_ptr_t event_handler; spe_event_unit_t event; spe_context_ptr_t speid; unsigned int entry = SPE_DEFAULT_ENTRY; event_handler = spe_event_handler_create(); // cerem activarea evenimentelor pt acest context speid = spe_context_create(SPE_EVENTS_ENABLE, NULL); spe_program_load(speid, &spu_mailbox); spe_context_run(speid, &entry, 0, (void*)0, (void*)55, NULL); // inregistram evenimentele dorite event.events = SPE_EVENT_OUT_INTR_MBOX; event.spe = speid; spe_event_handler_register(event_handler, &event); // asteptam blocant aparitia unui eveniment spe_event_wait(event_handler, &event, 1, -1); // citim valoarea mailbox-ului din eveniment; // nu va bloca deoarece aparitia evenimentului // inseamna ca o valoare este disponibila spe_out_intr_mbox_read(event.spe, &mbox_data, 1, SPE_MBOX_ALL_BLOCKING); printf("[PPU] SPU 0 sent data = %d\n", (int)mbox_data); spe_context_destroy(speid); spe_event_handler_destroy(event_handler); return 0; } | #include <stdio.h> #include <spu_mfcio.h> int main(unsigned long long speid, unsigned long long argp, unsigned long long envp) { (void)speid; // trimitem o singura valoare unui mailbox // gol, astfel apelul nu se va bloca // niciodata si nu putem avea deadlock printf("[SPU %ld] sending data = %d ...\n", (long)argp, (int)envp); spu_write_out_intr_mbox((unsigned int)envp); return 0; } |
Arhiva cu programul complet se poate descarca de aici.