Forum: Mikrocontroller und Digitale Elektronik Out of Memory Error im STM32 mit LWIP


von Daniel F. (lmdaniel999)


Lesenswert?

Hallo,

ich habe ein Problem mit einem STM32 und dem LwIP-Stack:
Nach einigen Empfangs-Versuchen meldet die Receiver-Funktion "Out of 
Memory". Welcher Speicher ist das und wie lösche ich den?

Folgendes habe ich gemacht:
Als Basis dient ein Example von ST, ein TCP-echo-server.
Den habe ich auf einem STM32F429 Eval Board am laufen.
Das Ganze läuft mit RTOS.

Ich erzeuge eine netconn mit "netconn_new", binde diese an einen lokalen 
Port ("netconn_bind") und verbinde mich mit einem Ziel-Gerät 
("netconn_connect"). Dann wird ein Befehl mit "netconn_write" an das 
Ziel-Gerät geschickt, um eine Antwort anzufordern. Das funktioniert 
alles und ab diesem Punkt sendet das Ziel-Gerät zyklisch.

Sobald das nun durchgelaufen ist, beende ich diesen thread, der das 
alles gemacht hat und starte die "netconn_recv" Funktion im neuen 
Thread.
Nun kommen dort auch ein paar Daten an.

Diese Daten hole ich mit "netbuf_copy" aus dem Buffer der 
"netconn_recv"-Funktion. Das sind pro empfangener Nachricht ungefähr 20 
Byte...

Das Problem ist, dass nach zwei bis drei empfangenen "Paketen" die 
"netconn_recv" Funktion den Error "ERR_MEM" zurückgibt. In der Header 
Datei steht "Out of memory error" dabei.

Ich habe versucht, nach dem Lesen der Daten mit "netbuf_free" Platz zu 
schaffen. Leider bringt das nichts.

Ich verstehe nicht, welcher Speicher nun voll sein soll und wie ich den 
leeren kann... Wie bekomme ich das raus?

Vielen Dank!

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Du könntest mal den Heap vergrössern, um zu sehen, ob das Problem sich 
verschiebt. Ich bin kein Mensch für RTOS, aber sonst kann man das meist 
im Linker Skript justieren. Das ist sicher kein Dauerzustand, kann aber 
Hinweise geben.
Generell hast du inkl. CCRam 256kByte, ohne CCRAM (RTOS benutzt den?) 
sind dann 192kByte. Sicher nicht die Welt, wenn das Board nicht noch 
externen RAM bereitstellt.

von fgd (Gast)


Lesenswert?

dein code würde helfen ...

lwip kopiert die daten in diverse pbufs
beendest du die verindung musst du diese auch wieder freigeben.

warum nicht die Socket API ?

von Daniel F. (lmdaniel999)


Lesenswert?

fgd schrieb:
> dein code würde helfen ...
>
> lwip kopiert die daten in diverse pbufs
> beendest du die verindung musst du diese auch wieder freigeben.
>
> warum nicht die Socket API ?

Ich muss gestehen, dass ich nicht sicher bin! :-)
Die netconn habe ich verwendet, weil die im Beispiel geklappt hat.
Vorher hab ich die RAW-API getestet, aber da bin ich gescheitert.
Mir sind die Unterschiede zwischen der netconn und der Socket API nicht 
bewusst. Wo kann ich einen Vergleich nachlesen? Aus der Doku werde ich 
nicht schlau...

Hier der Codeausschnitt, der empfängt:
1
void receive(void){
2
  err_t err;
3
  struct netbuf *buf;
4
5
  while(1){
6
    while((err = netconn_recv(conn, &buf)) == ERR_OK) {
7
        if (buf == NULL)
8
        {
9
          LCD_UsrLog ("No Data received!\n");
10
        }
11
        else
12
        {
13
          uint8_t dataReceived[16];
14
          netbuf_copy (buf, &dataReceived, 16);
15
          int i=0;
16
          for(i=0 ; i<16 ; i++){
17
            LCD_UsrLog ("Received Data: ");
18
            LCD_UsrLog ("%X\n",dataReceived[i]);
19
          }
20
          count++;
21
          printf("Receive-counter: %d\n", count);
22
          printf("Netconn-state: %d\n", err);
23
        }
24
      }
25
      printf("Netconn-ERROR: %d\n", err);
26
      osDelay(3000);
27
  }
28
}

Nochmal zur Erklärung:
Die Verbindung mit dem Partner wird aufgebaut, und es wird eine 
Nachricht geschickt. Ab dann antwortet der Partner zyklisch. Ich beende 
den Thread, der die Verbindung aufgebaut hatte und mache einen neuen 
Thread, der aus der Fkt. oben besteht. Ab dann kann ich nur zwei bis 
drei Antworten empfangen, dann ist der Puffer voll.

Eine Idee, wie ich den leere?
Danke! :-)

: Bearbeitet durch User
von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Daniel F. schrieb:
>
> Nochmal zur Erklärung:
> Die Verbindung mit dem Partner wird aufgebaut, und es wird eine
> Nachricht geschickt. Ab dann antwortet der Partner zyklisch. Ich beende
> den Thread, der die Verbindung aufgebaut hatte und mache einen neuen
> Thread, der aus der Fkt. oben besteht. Ab dann kann ich nur zwei bis
> drei Antworten empfangen, dann ist der Puffer voll.
>
> Eine Idee, wie ich den leere?
> Danke! :-)

Definiere "thread." Die meisten RTOS kennen keine threads, höchstens 
tasks. Wenn Du dynamisch neue tasks erzeugst, gehen dabei jedes Mal 
zumindestens der task stack und der Speicher für den TCB vom dynamischen 
heap weg. Bist Du sicher, dass Du den jeden nicht benutzten task 
komplett mit allem dealloziierst, wenn Du ihn nicht mehr benötigst 
("beenden" allein reicht bei vielen RTOS nicht zum dealloziieren)? 
Selbst wenn Du das tust, kannst Du in Fragmentierungsprobleme laufen.

Der obige Code ist unvollständig. Die Infrastruktur zum Bauen und 
löschen deiner "threads" sollte auch noch veröffentlicht werden.

: Bearbeitet durch User
von Daniel F. (lmdaniel999)


Lesenswert?

Ruediger A. schrieb:
> Daniel F. schrieb:
>>
>> Nochmal zur Erklärung:
>> Die Verbindung mit dem Partner wird aufgebaut, und es wird eine
>> Nachricht geschickt. Ab dann antwortet der Partner zyklisch. Ich beende
>> den Thread, der die Verbindung aufgebaut hatte und mache einen neuen
>> Thread, der aus der Fkt. oben besteht. Ab dann kann ich nur zwei bis
>> drei Antworten empfangen, dann ist der Puffer voll.
>>
>> Eine Idee, wie ich den leere?
>> Danke! :-)
>
> Definiere "thread." Die meisten RTOS kennen keine threads, höchstens
> tasks. Wenn Du dynamisch neue tasks erzeugst, gehen dabei jedes Mal
> zumindestens der task stack und der Speicher für den TCB vom dynamischen
> heap weg. Bist Du sicher, dass Du den jeden nicht benutzten task
> komplett mit allem dealloziierst, wenn Du ihn nicht mehr benötigst
> ("beenden" allein reicht bei vielen RTOS nicht zum dealloziieren)?
> Selbst wenn Du das tust, kannst Du in Fragmentierungsprobleme laufen.
>
> Der obige Code ist unvollständig. Die Infrastruktur zum Bauen und
> löschen deiner "threads" sollte auch noch veröffentlicht werden.

Sicher bin ich nicht. Habe mit RTOS noch nicht so viel gemacht.
Vielleicht liegt mein Problem auch daran...
Hier der Code:

Die Main: (Aus CubeMX erzeugt...)
1
int main(void)
2
{
3
  /* STM32F4xx HAL library initialization:
4
       - Configure the Flash prefetch, instruction and Data caches
5
       - Configure the Systick to generate an interrupt each 1 msec
6
       - Set NVIC Group Priority to 4
7
       - Global MSP (MCU Support Package) initialization
8
     */
9
  HAL_Init();  
10
  
11
  /* Configure the system clock to 180 MHz */
12
  SystemClock_Config();
13
  
14
  /* Init task */
15
  osThreadDef(Start, StartThread, osPriorityNormal, 0, configMINIMAL_STACK_SIZE * 2);
16
  osThreadCreate (osThread(Start), NULL);
17
  
18
  /* Start scheduler */
19
  osKernelStart();
20
  
21
  /* We should never get here as control is now taken by the scheduler */
22
  for( ;; );
23
}

Dann der StartThread:
1
static void StartThread(void const * argument)
2
{
3
  /* Initialize LCD and LEDs */
4
  BSP_Config();
5
  
6
  /* Create tcp_ip stack thread */
7
  tcpip_init(NULL, NULL);
8
  
9
  /* Initialize the LwIP stack */
10
  Netif_Config();
11
   
12
  /* Notify user about the network interface config */
13
  User_notification(&gnetif);
14
  
15
  osThreadDef(ConnectingThread, connect, osPriorityBelowNormal, 0, configMINIMAL_STACK_SIZE * 2);
16
  osThreadCreate (osThread(ConnectingThread), &gnetif);
17
18
  osThreadDef(ReceiverThread, receive, osPriorityHigh, 0, configMINIMAL_STACK_SIZE);
19
  osThreadCreate(osThread(ReceiverThread), NULL);
20
21
  for( ;; )
22
  {
23
    /* Delete the Init Thread */
24
    osThreadTerminate(NULL);
25
  }
26
}
Hier habe ich noch den "ReceiverThread" ergänzt. Standardmäßig war da 
nur ein Thread. Zuerst war alles in einem Thread. Aber zum Testen der 
Speicher Probleme habe ich dann einen neuen Thread erzeugt, weil ich 
dachte, dass der das dann schneller abarbeiten kann... Hat daran wohl 
nicht gelegen.

Zuerst wird im Startthread "BSP_Config" aufgerufen: (Nur Texte geändert)
1
static void BSP_Config(void)
2
{
3
  /* Configure LED1, LED2 and LED4 */
4
  BSP_LED_Init(LED1);
5
  BSP_LED_Init(LED2);
6
  BSP_LED_Init(LED4);
7
  
8
  /* Init IO Expander */
9
  BSP_IO_Init();
10
  /* Enable IO Expander interrupt for ETH MII pin */
11
  BSP_IO_ConfigPin(MII_INT_PIN, IO_MODE_IT_FALLING_EDGE);
12
  
13
#ifdef USE_LCD
14
15
  /* Initialize the LCD */
16
  BSP_LCD_Init();
17
  
18
  /* Initialize the LCD Layers */
19
  BSP_LCD_LayerDefaultInit(1, LCD_FB_START_ADDRESS);
20
  
21
  /* Set LCD Foreground Layer  */
22
  BSP_LCD_SelectLayer(1);
23
  
24
  BSP_LCD_SetFont(&LCD_DEFAULT_FONT);
25
  
26
  /* Initialize LCD Log module */
27
  LCD_LOG_Init();
28
  
29
  /* Show Header and Footer texts */
30
  LCD_LOG_SetHeader((uint8_t *)"Test Application");
31
  LCD_LOG_SetFooter((uint8_t *)"Communicator");
32
  
33
  LCD_UsrLog ("Ethernet initialization ...\n");
34
35
#endif
36
}

Dann kommt "tcpip_init" und "Netif_Config", welche ich auch nicht 
geändert habe:
1
static void Netif_Config(void)
2
{
3
  ip_addr_t ipaddr;
4
  ip_addr_t netmask;
5
  ip_addr_t gw;
6
  
7
  IP_ADDR4(&ipaddr,IP_ADDR0,IP_ADDR1,IP_ADDR2,IP_ADDR3);
8
  IP_ADDR4(&netmask,NETMASK_ADDR0,NETMASK_ADDR1,NETMASK_ADDR2,NETMASK_ADDR3);
9
  IP_ADDR4(&gw,GW_ADDR0,GW_ADDR1,GW_ADDR2,GW_ADDR3);
10
  
11
  /* - netif_add(struct netif *netif, ip_addr_t *ipaddr,
12
  ip_addr_t *netmask, ip_addr_t *gw,
13
  void *state, err_t (* init)(struct netif *netif),
14
  err_t (* input)(struct pbuf *p, struct netif *netif))
15
  
16
  Adds your network interface to the netif_list. Allocate a struct
17
  netif and pass a pointer to this structure as the first argument.
18
  Give pointers to cleared ip_addr structures when using DHCP,
19
  or fill them with sane numbers otherwise. The state pointer may be NULL.
20
  
21
  The init function pointer must point to a initialization function for
22
  your ethernet netif interface. The following code illustrates it's use.*/
23
  
24
  netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);
25
  
26
  /*  Registers the default network interface. */
27
  netif_set_default(&gnetif);
28
  
29
  if (netif_is_link_up(&gnetif))
30
  {
31
    /* When the netif is fully configured this function must be called.*/
32
    netif_set_up(&gnetif);
33
  }
34
  else
35
  {
36
    /* When the netif link is down this function must be called */
37
    netif_set_down(&gnetif);
38
  }
39
40
  /* Set the link callback function, this function is called on change of link status*/
41
  netif_set_link_callback(&gnetif, ethernetif_update_config);
42
  
43
  /* create a binary semaphore used for informing ethernetif of frame reception */
44
  osSemaphoreDef(Netif_SEM);
45
  Netif_LinkSemaphore = osSemaphoreCreate(osSemaphore(Netif_SEM) , 1 );
46
  
47
  link_arg.netif = &gnetif;
48
  link_arg.semaphore = Netif_LinkSemaphore;
49
  /* Create the Ethernet link handler thread */
50
#if defined(__GNUC__)
51
  osThreadDef(LinkThr, ethernetif_set_link, osPriorityNormal, 0, configMINIMAL_STACK_SIZE * 5);
52
#else
53
  osThreadDef(LinkThr, ethernetif_set_link, osPriorityNormal, 0, configMINIMAL_STACK_SIZE * 2);
54
#endif
55
  osThreadCreate (osThread(LinkThr), &link_arg);
56
}

Das ist der Connecting Thread:
1
void connect(){
2
  err_t err, err2,err3;
3
  ip_addr_t destIP;
4
5
  /* Create a new connection identifier. */
6
  LCD_UsrLog ("Creating new connection...\n");
7
    conn = netconn_new(NETCONN_TCP);
8
9
    if (conn!=NULL)
10
    {
11
      LCD_UsrLog ("Connection created!\n");
12
      /* Bind connection to well known port number 24685. */
13
      LCD_UsrLog ("Bind the connection to own port 24685...\n");
14
      err = netconn_bind(conn, NULL, 24685);
15
16
      if (err == ERR_OK)
17
      {
18
        LCD_UsrLog ("Connection bound!\n");
19
        IP_ADDR4(&destIP,IP_DEST0,IP_DEST1,IP_DEST2,IP_DEST3);
20
      uint8_t iptxt[20];
21
      sprintf((char *)iptxt, "%s", ip4addr_ntoa((const ip4_addr_t *)&destIP));
22
      LCD_UsrLog ("Connect to destination-IP address %s at port 24685...\n", iptxt);
23
      err2 = netconn_connect(conn, &destIP, 24685);
24
25
      if (err2 == ERR_OK)
26
      {
27
        LCD_UsrLog ("Connection established!\n");
28
        err3 = send();
29
30
        if (err3 == ERR_OK)
31
        {
32
          LCD_UsrLog ("Starting new thread...\n");
33
          
34
          osThreadTerminate(NULL);
35
        }
36
        else
37
        {
38
          LCD_UsrLog ("Sending failed!\n");
39
        }
40
41
      }
42
      else
43
      {
44
        LCD_UsrLog ("Connecting failed!\n");
45
      }
46
      }
47
    }
48
49
50
      /* Tell connection to go into listening mode. */
51
}

Hier ist die Send-Funktion, die aufgerufen wird, wenn eine Verbindung 
aufgebaut wurde:
1
err_t send(void){
2
  err_t err;
3
  LCD_UsrLog ("Sending data...\n");
4
  uint8_t dataToSend[14] = {0x4E,0x53,0x2C,0x30,0x2C,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x0D};
5
  err = netconn_write(conn, &dataToSend, 14,NETCONN_NOCOPY);
6
  if (err == ERR_OK)
7
  {
8
    LCD_UsrLog ("Data sent!\n");
9
  }
10
  return err;
11
}

Dann der Receiver im separaten Thread:
1
void receive(void){
2
  err_t err;
3
  struct netbuf *buf;
4
5
  while(1){
6
    while((err = netconn_recv(conn, &buf)) == ERR_OK) {
7
        if (buf == NULL)
8
        {
9
          LCD_UsrLog ("No Data received!\n");
10
        }
11
        else
12
        {
13
          uint8_t dataReceived[16];
14
          netbuf_copy (buf, &dataReceived, 16);
15
          int i=0;
16
          for(i=0 ; i<16 ; i++){
17
            LCD_UsrLog ("Received Data: ");
18
            LCD_UsrLog ("%X\n",dataReceived[i]);
19
          }
20
          count++;
21
          printf("Receive-counter: %d\n", count);
22
          printf("Netconn-state: %d\n", err);
23
        }
24
      }
25
      printf("Netconn-ERROR: %d\n", err);
26
      osDelay(3000);
27
  }
28
}

Als Variablen sind noch folgende Sachen deklariert:
1
struct netif gnetif; /* network interface structure */
2
/* Semaphore to signal Ethernet Link state update */
3
osSemaphoreId Netif_LinkSemaphore = NULL;
4
/* Ethernet link thread Argument */
5
struct link_str link_arg;
6
struct netconn *conn;

Hoffe, es passt nun mit dem Code! :-)

von Eddy (Gast)


Lesenswert?

Ich kenne die netconn-API nicht so, aber könnte es nicht sein, dass man 
innerhalb des Structs 'buf' noch Speicher für den Empfang allozieren 
muss?

Außerdem war bei mir mal ein Problem mit FreeRTOS und Sockets, dass die 
Buffer (wie bei Dir 'dataToSend' und 'dataReceived' lokale Variablen 
waren. Ich würde sie testweise mal global definieren.

von Daniel F. (lmdaniel999)


Lesenswert?

Wie mach ich das mit dem Speicher?
Im struct sehe ich nur ne Adresse, einen Port und zwei Pointer.

Aber stimmt eigentlich... Wo wird denn definiert, wieviel Speicher der 
mit dem Buffer bekommt?

Das mit den Variablen werde ich testen...
Für mein Verständnis:
dataToSend bleibt ja immer fest. --> Irrelevant, oder?
dataReceived sollte doch immer überschrieben werden, mit dem letzten 
Wert aus dem Buffer, oder?

von Peter (Gast)


Lesenswert?

Wo genau wird receive() aufgerufen? Ich sehe nur die Definition. Oder 
wird es automatisch von LwIP gemacht?

Außerdem ist es immer noch nicht offensichtlich, wie der Thread beendet 
wird, in der while(1)-Schleife innerhalb von receive() gibt es keine 
einzige Stelle, die die Schleife beenden könnte.

Ich sehe auch keine Stelle, wo netconn_delete() aufgerufen wird, nachdem 
die Verbindung beendet wurde. Kann es sein, dass dies garnicht passiert?

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Daniel F. schrieb:
> Ruediger A. schrieb:
>> Daniel F. schrieb:
>>>
>>> Nochmal zur Erklärung:
>>> Die Verbindung mit dem Partner wird aufgebaut, und es wird eine
>>> Nachricht geschickt. Ab dann antwortet der Partner zyklisch. Ich beende
>>> den Thread, der die Verbindung aufgebaut hatte und mache einen neuen
>>> Thread, der aus der Fkt. oben besteht. Ab dann kann ich nur zwei bis
>>> drei Antworten empfangen, dann ist der Puffer voll.
>>>
>>> Eine Idee, wie ich den leere?
>>> Danke! :-)
>>
>> Definiere "thread." Die meisten RTOS kennen keine threads, höchstens
>> tasks. Wenn Du dynamisch neue tasks erzeugst, gehen dabei jedes Mal
>> zumindestens der task stack und der Speicher für den TCB vom dynamischen
>> heap weg. Bist Du sicher, dass Du den jeden nicht benutzten task
>> komplett mit allem dealloziierst, wenn Du ihn nicht mehr benötigst
>> ("beenden" allein reicht bei vielen RTOS nicht zum dealloziieren)?
>> Selbst wenn Du das tust, kannst Du in Fragmentierungsprobleme laufen.
>>
>> Der obige Code ist unvollständig. Die Infrastruktur zum Bauen und
>> löschen deiner "threads" sollte auch noch veröffentlicht werden.
>
...
>
> Hoffe, es passt nun mit dem Code! :-)

Nein, es fehlen die RTOS spezifischen Implementationen der os...() 
Funktionen, speziell der osThreadTerminate() Funktion.

Nach grober Durchsicht des Codes scheint es mir, dass Du den typischen 
Fehler begehst, Alles in separaten threads machen zu wollen. Das macht 
gerade in linearen Protokollen wenig Sinn. Die meisten 
Netzwerkprotokolle sind Halb Duplex, und damit braucht man keine 
getrennten Sender und Receiver threads (bei vielen OS, z.B. lwip mit 
sockets, ist es sogar so, dass Du die selbe Verbindung nicht im mehreren 
threads nicht nutzen kannst, ohne in die wunderbare Welt der 
Nebenläufigkeitsprobleme bis Oberkante Unterlippe einzutauchen). Da beim 
Multithreading die Regel gilt "zu viel ist mindestens genau so schlecht 
wie zu wenig," solltest Du Dir über die Architektur ein paar Gedanken 
machen.

Deine Applikation ist ein TCP Client, d.h. Du brauchst auch keinen 
separaten thread für das Abwickeln der Kommunikation nach etablierter 
Verbindung. Bei Servern kann man das machen, wenn man mehrfache 
Verbindungen nebenläufig zulassen will, aber bei Clients ist das 
komplett unnötig.

Ich weiss, dass ich jetzt etwas Augenrollen in der Forumsgemeinde 
erzwingen werde, aber an dieser Stelle ist ein Hinweis auf Kapitel 4 und 
6 meines Buches erlaubt. Da gehe ich sehr detailliert darauf ein, wie 
man solche Sachen in RTOS umsetzen kann.

: Bearbeitet durch User
von Eddy (Gast)


Lesenswert?

Daniel F. schrieb:
> Wie mach ich das mit dem Speicher?
> Im struct sehe ich nur ne Adresse, einen Port und zwei Pointer.
>
> Aber stimmt eigentlich... Wo wird denn definiert, wieviel Speicher der
> mit dem Buffer bekommt?
Weiß ich leider auch nicht (und wie gesagt auch nicht, ob das überhaupt 
notwendig ist)!

>
> Das mit den Variablen werde ich testen...
> Für mein Verständnis:
> dataToSend bleibt ja immer fest. --> Irrelevant, oder?
> dataReceived sollte doch immer überschrieben werden, mit dem letzten
> Wert aus dem Buffer, oder?
Ob die Variable immer denselben Inhalt hat oder sie immer überschrieben 
wird, tut ja bzgl. der Speicherstruktur wahrscheinlich nichts zur Sache.
Einfach mal global ausprobieren!

von fgd (Gast)


Lesenswert?

was mir auffällt ist das du die tasks zwar erstellst aber diese laufen 
ins ende
d.h. der Speicher wird ggf nie freigegeben

beim beenden ( wirklichem kill ) musst du  dem RTOS auch sagen das er 
den speicher freigeben soll.
1
void Task(void)
2
{
3
   bool_t abbruch = 0;
4
   // tu was vor dem loop 
5
6
   while(1)
7
   {
8
       // task lebt so lange im loop bis abbruch gefordert wird
9
       if( abbruch == 1)
10
           break;
11
12
       // tu was im Task 
13
14
   }
15
   // speicher freigeben   
16
   vTaskDelete(NULL); 
17
}
18
19
20
21
int main( int )
22
{
23
  // init HAL usw .. 
24
25
26
  xTaskCreate( Task, "task", 256 , NULL , 1 , NULL );
27
  vTaskStartScheduler();
28
}
Mod: Ende-Tag C-Code ergänzt

: Bearbeitet durch Moderator
von Daniel F. (lmdaniel999)


Lesenswert?

Ruediger A. schrieb:
> Nein, es fehlen die RTOS spezifischen Implementationen der os...()
> Funktionen, speziell der osThreadTerminate() Funktion.

Da diese Funktionen aus den Examples kommen, habe ich diese nicht 
angepackt:
1
osStatus osThreadTerminate (osThreadId thread_id)
2
{
3
#if (INCLUDE_vTaskDelete == 1)
4
  vTaskDelete(thread_id);
5
  return osOK;
6
#else
7
  return osErrorOS;
8
#endif
9
}

Ruediger A. schrieb:
> Nach grober Durchsicht des Codes scheint es mir, dass Du den typischen
> Fehler begehst, Alles in separaten threads machen zu wollen. Das macht
> gerade in linearen Protokollen wenig Sinn.

Ich hatte zuerst alles in einem Thread, dachte aber, dass der zweite 
neue und leere Thread schneller sein könnte.... Denkfehler...

Für diese Anwendung brauche ich keine Threads, das ist richtig. 
Theoretisch kann das alles linear ablaufen. Allerdings hab ich das ohne 
RTOS mit der RAW API probiert und bin gescheitert. Hab dazu keine 
Examples gefunden, die ich nutzen konnte...

Sollte also jemand ein Example ohne RTOS haben, wo eine TCP Verbindung 
aufgebaut wird und Daten gesendet / empfangen werden, dann als her 
damit... :-)
Ist die RAW API dafür geeignet? Macht das Sinn, das damit zu machen?

Peter schrieb:
> Wo genau wird receive() aufgerufen?

Das ist der "ReceiverThread" im "StartThread" und läuft parallel. Soweit 
ich es verstanden habe...

Peter schrieb:
> Außerdem ist es immer noch nicht offensichtlich, wie der Thread beendet
> wird, in der while(1)-Schleife innerhalb von receive() gibt es keine
> einzige Stelle, die die Schleife beenden könnte.
>
> Ich sehe auch keine Stelle, wo netconn_delete() aufgerufen wird, nachdem
> die Verbindung beendet wurde. Kann es sein, dass dies garnicht passiert?

Das ist richtig. Der Thread soll theoretisch so lange laufen, wie das 
System an ist. Wenn der Code läuft, würde ich noch ein reconnect 
ergänzen. Das ist aber oben noch nicht implementiert.
Aktuell wird weder der Thread beendet, noch die Verbindung gelöscht!

fgd schrieb:
> was mir auffällt ist das du die tasks zwar erstellst aber diese laufen
> ins ende
> d.h. der Speicher wird ggf nie freigegeben
>
> beim beenden ( wirklichem kill ) musst du  dem RTOS auch sagen das er
> den speicher freigeben soll.

Das ist wohl in der "osThreadTerminate" Funktion so implementiert.
Allerdings fällt mir aktuell auf, dass ich die ID übergeben muss, was 
ich nicht mache... Der Befehl wurde im Example aufgerufen für den Init 
Thread. Dachte der gilt für den jeweiligen Thread....

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Daniel F. schrieb:
> Ruediger A. schrieb:
>> Nach grober Durchsicht des Codes scheint es mir, dass Du den typischen
>> Fehler begehst, Alles in separaten threads machen zu wollen. Das macht
>> gerade in linearen Protokollen wenig Sinn.
>
> Ich hatte zuerst alles in einem Thread, dachte aber, dass der zweite
> neue und leere Thread schneller sein könnte.... Denkfehler...
>
> Für diese Anwendung brauche ich keine Threads, das ist richtig.
> Theoretisch kann das alles linear ablaufen. Allerdings hab ich das ohne
> RTOS mit der RAW API probiert und bin gescheitert. Hab dazu keine
> Examples gefunden, die ich nutzen konnte...
>
> Sollte also jemand ein Example ohne RTOS haben, wo eine TCP Verbindung
> aufgebaut wird und Daten gesendet / empfangen werden, dann als her
> damit... :-)
> Ist die RAW API dafür geeignet? Macht das Sinn, das damit zu machen?
>

https://www.springer.com/de/book/9783658148492

mittig runterscrollen, link "Zusatzmaterial" klicken, entpacken. Das 
Beispiel von Kapitel 7 kannst Du als Blaupause nehmen (allerdings hat 
das genau den Fehler, auf denselben socket von 2 threads zuzugreifen, 
sh. mein blog für Ergänzungen).

Das raw API kannst Du nehmen, aber Du verlierst auch nichts mit dem BSD 
API. Dafür gewinnst Du aber Kompatibilität; gefühlte 90% aller nicht 
über high level Abstraktionslayer implementierte TCP/IP Kommunikationen 
gehen über das BSD API.

von Daniel F. (lmdaniel999)


Lesenswert?

Ruediger A. schrieb:
> mittig runterscrollen, link "Zusatzmaterial" klicken, entpacken. Das
> Beispiel von Kapitel 7 kannst Du als Blaupause nehmen (allerdings hat
> das genau den Fehler, auf denselben socket von 2 threads zuzugreifen,
> sh. mein blog für Ergänzungen).
>
> Das raw API kannst Du nehmen, aber Du verlierst auch nichts mit dem BSD
> API. Dafür gewinnst Du aber Kompatibilität; gefühlte 90% aller nicht
> über high level Abstraktionslayer implementierte TCP/IP Kommunikationen
> gehen über das BSD API.

Danke für die Infos!

Werde mir das Beispiel ansehen...
Ich habe gesehen, dass es eine Neuauflage von dem Buch gibt, welche aber 
noch nicht zu kaufen ist. Ab wann wird die Verfügbar sein? Lohnt sich 
dann bestimmt nicht mehr, die erste Auflage zu kaufen, oder?

von Eddy (Gast)


Lesenswert?

Ruediger A. schrieb:
> Das raw API kannst Du nehmen, aber Du verlierst auch nichts mit dem BSD
> API. Dafür gewinnst Du aber Kompatibilität; gefühlte 90% aller nicht
> über high level Abstraktionslayer implementierte TCP/IP Kommunikationen
> gehen über das BSD API.

Mit der RAW-API kann man doch als Gegenstück auch einfach Sockets 
benutzen (z.B. einen Windows-/Linux-Client/-Server).

von Andreas M. (amesser)


Lesenswert?

Ruediger A. schrieb:
> Definiere "thread." Die meisten RTOS kennen keine threads, höchstens
> tasks. Wenn Du dynamisch neue tasks erzeugst, gehen dabei jedes Mal
> zumindestens der task stack und der Speicher für den TCB vom dynamischen
> heap weg. Bist Du sicher, dass Du den jeden nicht benutzten task

Das kann man interpretieren wie man will. Eine Task in einem Embedded 
System mit RTOS entspricht viel eher einem Thread auf einem Posix System 
als einer Task/Prozess eines Posix Systems: Auf Embedded Systemen gibt 
es meistens keine eigenen Addressbereiche, so das jeder auf alles zu 
greifen kann, sowie das bei Threads in einem PC Programm ist, PC 
Programme untereinander können im allgemeinen jedoch nicht auf den 
gleichen Speicher zugreifen.

Zum Thema:

Generell ist es keine gute Idee in einem Embedded System Tasks/Threads 
regelmäßig zu starten und zu beenden. Das führt fast immer zu 
Heap-Fragmentierung. Dadurch wird auch nichts schneller. (Sonder eher 
langsamer)

Als kleinen Hinweis aus eigener Erfahren: TCP ist nicht dafür ausgelegt, 
viele kleine Datenhäppchen unidirektional zu übertragen. Das hängt mit 
der Funktionsweise des Protokolls zusammen. (Delayed Ack, Naggle) 
Entweder bei kleinen Daten im Ping-Pong Häppchen hin und her schicken 
oder große Datenmengen möglichst groß, am Stück dem Stack übergeben.

Wir verwenden die lwip sockets und pbufs direkt. Wenn man sich da erst 
mal dran gewöhnt hat, ist das nicht schlecht, da man sich das hin und 
her Kopieren an vielen Stellen sparen kann.

Du musst nach netconn_recv(), wenn Du die Daten verarbeitet hast 
pbuf_free benutzen um den Puffer wieder freizugeben:
1
struct pbuf *p;
2
netconn_recv(.., &p);
3
4
if(NULL != p)
5
{
6
  /* daten benutzen */
7
  p->payload ...
8
9
  pbuf_free(p);
10
  p = NULL;
11
}

Edit: Wir verwenden gar nicht die Raw Api, sondern gehen lowlevel auf 
die sockets/funktionen drauf.

: Bearbeitet durch User
von Fitzebutze (Gast)


Lesenswert?

Moin,

ich würde bei lwip ausschliesslich mit fixen DMA-Buffern arbeiten und 
generell darauf hinarbeiten, so wenig wie möglich malloc() zu verwenden.
Das kann allerdings darauf hinauslaufen, dass man sich dann doch einen 
eigenen TCP-Stack schreibt, die Architektur des LWIP ist nicht gerade 
optimal für DMA-Queues.

Andreas M. schrieb:
> Als kleinen Hinweis aus eigener Erfahren: TCP ist nicht dafür ausgelegt,
> viele kleine Datenhäppchen unidirektional zu übertragen. Das hängt mit
> der Funktionsweise des Protokolls zusammen. (Delayed Ack, Naggle)
> Entweder bei kleinen Daten im Ping-Pong Häppchen hin und her schicken
> oder große Datenmengen möglichst groß, am Stück dem Stack übergeben.

Unidirektional geht das ohne Probleme, da der Nagle auch kleine 
Datenpakete 'queue't. Kann man sich in Wireshark zumindest für den 
Linux-Kernel-Stack anschauen, was der LWIP macht, weiss ich nicht. Ist 
ja faktisch nur ein Stream. Da kriegt man auch eine anständige Datenrate 
hin. Schlechter ist Ping-Pong, das hat dann in UDP mehr Performance.

von Andreas M. (amesser)


Lesenswert?

Fitzebutze schrieb:
> Unidirektional geht das ohne Probleme, da der Nagle auch kleine
> Datenpakete 'queue't. Kann man sich in Wireshark zumindest für den
> Linux-Kernel-Stack anschauen, was der LWIP macht, weiss ich nicht. Ist
> ja faktisch nur ein Stream. Da kriegt man auch eine anständige Datenrate
> hin. Schlechter ist Ping-Pong, das hat dann in UDP mehr Performance.

Lass mal einen LwIP in der Standard-Config einfach nur 1 byte Packete an 
ein Windows senden. Du wirst bei ca ein bis zweistelligen Byte/s 
Durchsatz landen. Das Windows sendet auf Packete die unterhalb der MTU 
Größe sind erst nach ca. 200ms den Ack (Delayed Ack) weil der Windows 
Stack davon ausgeht, das wenn so wenig Daten ankommen, der Sender nichts 
mehr senden wird und zunächst die (Windows-) Empfängerapplikation was 
zurücksenden will. (Das Ack wird dann zusammen mit den Antwortdaten 
verschickt). Der Lwip Puffert aber die Daten solange das Ack noch nicht 
angekommen ist. (Wenn man byteweise verschickt gehen dem dann auch bald 
die puffer aus): Sobald man in der Windows Applikation anfängt auf die 
Empfangenen Daten jeweils mit einer 1Byte Response zu antworten steigt 
der Datendurchsatz massiv an, weil die Acks schneller geschickt werden.

Der Linux Netzwerkstack macht das eigentlich auch so, hat aber eine 
Heuristik die das Verhalten erkennt und den Delayed Ack abschaltet.

Unter Linux gibt es auch extra Socket Options um diesen Anwendungsfall 
(telnet, ssh) zu optimieren.

Deswegen sollte man bei TCP möglichst alle Daten auf einmal versenden 
(wenn man kann)

von Dirk (Gast)


Lesenswert?

Andreas M. schrieb:
> Lass mal einen LwIP in der Standard-Config

Weißt Du vielleicht, was man bei LwIP (STM32) umstellen muss, damit 
mehrere Pakete versendet werden können und nicht jedes einzelne durch 
ein ACK quittiert werden muss, bevor ein weiteres gesendet wird (bei mir 
klappt das partout nicht, auch mit 'tcp_output()' nicht)? TCP/IP an sich 
lässt das ja zu. Und LwIp soll das eigentlich lt. deren Forum auch 
können.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Daniel F. schrieb:
> Ruediger A. schrieb:
>> mittig runterscrollen, link "Zusatzmaterial" klicken, entpacken. Das
>> Beispiel von Kapitel 7 kannst Du als Blaupause nehmen (allerdings hat
>> das genau den Fehler, auf denselben socket von 2 threads zuzugreifen,
>> sh. mein blog für Ergänzungen).
>>
>> Das raw API kannst Du nehmen, aber Du verlierst auch nichts mit dem BSD
>> API. Dafür gewinnst Du aber Kompatibilität; gefühlte 90% aller nicht
>> über high level Abstraktionslayer implementierte TCP/IP Kommunikationen
>> gehen über das BSD API.
>
> Danke für die Infos!
>
> Werde mir das Beispiel ansehen...
> Ich habe gesehen, dass es eine Neuauflage von dem Buch gibt, welche aber
> noch nicht zu kaufen ist. Ab wann wird die Verfügbar sein? Lohnt sich
> dann bestimmt nicht mehr, die erste Auflage zu kaufen, oder?

Hallo Daniel,

ich habe das in einem anderen thread aufgegriffen, um diesen nicht OT zu 
führen:

Beitrag "Buch "Embedded Controller," ISBN-10 3658148497"

Danke!

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Dirk schrieb:
> Andreas M. schrieb:
>> Lass mal einen LwIP in der Standard-Config
>
> Weißt Du vielleicht, was man bei LwIP (STM32) umstellen muss, damit
> mehrere Pakete versendet werden können und nicht jedes einzelne durch
> ein ACK quittiert werden muss, bevor ein weiteres gesendet wird (bei mir
> klappt das partout nicht, auch mit 'tcp_output()' nicht)? TCP/IP an sich
> lässt das ja zu. Und LwIp soll das eigentlich lt. deren Forum auch
> können.

Eigentlich sollte das Nagleing default sein und mit der Option 
TCP_NODELAY deaktiviert werden.

Die Frage ist natürlich, welches Zeitverhalten zwischen den kleinen 
Paketen herrscht. Wenn Du ein Protokoll hast, bei dem ein peer ein 
mehrere wenige Bytes umfassendes Paket schickt (z.B. einen Master-Slave 
Polling request), der in der Regel auch nur weinge Bytes gross ist, 
möchtest Du nicht wirklich Nageln, da beim Nageln ein Timer aufgezogen 
wird, der erst beim Ablaufen alles was in der Zwischenzeit aufgelaufen 
ist schickt. Das ist aber mit vielen Protokollen inkompatibel, weil die 
gerade auf einen recht schnellen Umlauf getunt sind. Es gibt ausserdem 
ein Riesenpotential für subtile Probleme. Wenn z.B. ein serielles 
Master-Slave Protokoll 1:1 auf ein Netzwerk übertragen wird, passiert es 
oft, dass sich masterseitig Paketretransmissions anstauen, die dann 
plötzlich beim Slave en bulk ankommen. Was dann dazu führt, dass ACKs 
falsch zugeordnet werden, wenn keine eindeutigen Sequenznummern im 
Protokoll definiert sind. BTDT.

von Andreas M. (amesser)


Lesenswert?

Dirk schrieb:
> Andreas M. schrieb:
>> Lass mal einen LwIP in der Standard-Config
>
> Weißt Du vielleicht, was man bei LwIP (STM32) umstellen muss, damit
> mehrere Pakete versendet werden können und nicht jedes einzelne durch
> ein ACK quittiert werden muss, bevor ein weiteres gesendet wird (bei mir
> klappt das partout nicht, auch mit 'tcp_output()' nicht)? TCP/IP an sich
> lässt das ja zu. Und LwIp soll das eigentlich lt. deren Forum auch
> können.

Das ist der Nagle Mechanismus. Der Sorgt dafür das der LwIP bei 
tcp_output nichts sendet solange noch ein "Ack" aussteht außer es gibt 
mindest noch zwei offene Segmente. Siehe auch "tcp_do_output_nagle". 
Kann man abschalten, indem man auf dem socket das TF_NODELAY flag setzt. 
dann sollte es bei tcp_output direkt rauskommen:
1
tcp_nagle_disable(pcb)

oder
1
int optval = 1;
2
3
lwip_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval))

von Dirk (Gast)


Lesenswert?

Andreas M. schrieb:
> Das ist der Nagle Mechanismus. Der Sorgt dafür das der LwIP bei
> tcp_output nichts sendet solange noch ein "Ack" aussteht außer es gibt
> mindest noch zwei offene Segmente. Siehe auch "tcp_do_output_nagle".
> Kann man abschalten, indem man auf dem socket das TF_NODELAY flag setzt.
> dann sollte es bei tcp_output direkt rauskommen:
> tcp_nagle_disable(pcb)
>
> oder
> int optval = 1;
>
> lwip_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval))

Danke, aber löst wohl nicht mein Problem, denn hiernach
  http://lwip.100.n7.nabble.com/TCP-NODELAY-td6837.html
    Post 'Oct 15, 2008; 8:31pm'
macht TF_NODELAY dasselbe wie 'tcp_write()' gefolgt von 'tcp_output()', 
was bei mir ja scheitert (werde es aber trotzdem in Kürze mal 
ausprobieren, denn der Post ist ja von 2008, evtl. hat sich in LwIp 
seitdem was geändert).

von Johannes S. (Gast)


Lesenswert?

in Mbed wird auch der LwIP Stack benutzt, habe da gerade mal 
nachgesehen:
https://github.com/ARMmbed/mbed-os/blob/c8d777823560813457106b46904b99a8bff68fda/features/lwipstack/LWIPStack.cpp#L437
und
https://github.com/ARMmbed/mbed-os/blob/c8d777823560813457106b46904b99a8bff68fda/features/lwipstack/LWIPStack.cpp#L512

da werden unterschiedliche Funktionen für das Buffer holen in recv_from 
(UDP) und recv (TCP) benutzt. Für TCP muss man die Daten zusammensetzten 
weil die ja anders als bei UDP nicht in Paketen reinkommen.

von fgd (Gast)


Lesenswert?

mal ein Q&D beispiel mit socket API

1
int  http_port_init(void )
2
{
3
  struct sockaddr_in address;
4
  int sock;
5
6
  if ((sock = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP )) < 0){
7
    return -1;
8
  }
9
  address.sin_family     = AF_INET;
10
  address.sin_port     = htons(80);
11
  address.sin_addr.s_addr = INADDR_ANY;
12
  lwip_bind(sock, (struct sockaddr *)&address, sizeof (address));
13
14
  struct timeval interval = {1000 / 1000, (1000 % 1000) * 1000};
15
  lwip_setsockopt( sock , SOL_SOCKET, SO_SNDTIMEO, (char *)&interval, sizeof(struct timeval));
16
  lwip_setsockopt( sock , SOL_SOCKET, SO_RCVTIMEO, (char *)&interval, sizeof(struct timeval));
17
18
  int tmp = 1;
19
  lwip_setsockopt( sock , SOL_SOCKET, SO_REUSEADDR, (const char *) &tmp, sizeof( tmp ) );
20
  lwip_listen(sock,3);
21
  return sock;
22
}
23
24
void http_server_send( const int s, const void *data, size_t size ){
25
  int tmp  = strlen( (char*)data );
26
  while( tmp ){
27
    tmp -= lwip_write( s , data , tmp  );
28
  }
29
}
30
31
const char content_http_200[] ={"HTTP/1.0 200 OK\r\nConnection: Close\r\nContent-Length: %u\r\nContent-Type: text/html; charset=utf-8\r\n\r\n%s"};
32
const char content_http[] ={ "hier steht HTML ..." };
33
34
static void http_server_socket_thread(void *arg)
35
{
36
  sock = http_port_init();
37
  if( sock < 0 ){
38
    goto exit;
39
  }
40
  while ( sock > 0){
41
    int conn = lwip_accept( sock ,NULL, NULL );
42
    if( conn > 0  )
43
    {
44
      char *buf;
45
      buf  = pvPortMalloc( (8*1024) );
46
      
47
      if( lwip_read(conn, buf , 1460 ) )
48
      {
49
        if(( strncmp(buf, "GET / ",6) == 0 ) || ( strncmp(buf, "GET /index.html", 15) == 0 ) )
50
        {
51
          sprintf( buf , content_http_200 , (int)strlen( content_http) , content_http  );
52
          http_server_send( conn , buf, strlen(buf));
53
54
        }
55
        else
56
        {
57
          http_server_send( conn , content_http_404 , strlen(content_http_404) );
58
        }
59
      }
60
      vPortFree(buf);
61
      lwip_close( conn );
62
    }
63
  }
64
exit:
65
  vTaskDelete(NULL);
66
}
67
68
void http_server_socket_init( void )
69
{
70
  xTaskCreate( http_server_socket_thread, "HTTP", 320 ,NULL , 1, NULL );
71
}

von fgd (Gast)


Lesenswert?

vergessen ...
1
const char content_http_404[] ={"HTTP/1.0 404 Not found\r\nServer: testserver\r\nConnection: Close\r\n\r\n<html><body><h2>404: The requested file cannot be found.</h2></body></html>\r\n"};

von Johannes S. (Gast)


Lesenswert?

fgd schrieb:
> if( lwip_read(conn, buf , 1460 ) )

das ist aber ein optimistischer Ansatz, da hofft man einen Request am 
Stück zu bekommen. Aber das garantiert dir TCP und auch lwip_read nicht.

von fgd (Gast)


Lesenswert?

Johannes S. schrieb:
> fgd schrieb:
>> if( lwip_read(conn, buf , 1460 ) )
>
> das ist aber ein optimistischer Ansatz, da hofft man einen Request am
> Stück zu bekommen. Aber das garantiert dir TCP und auch lwip_read nicht.

richtig.
deswegen Quick&Dirty

der vollständigkeit halter muss man hier erweitern

von Daniel F. (lmdaniel999)


Lesenswert?

Hier geht es nochmal speziell um die Buffer, allerdings mit der RAW API: 
Beitrag "LwIP TCP: pbuf definieren und/oder leeren?"

von Alexander M. (a_lexander)


Lesenswert?

Hallo Zusammen.

Weil sich in diesem Thread wohl die Experten der Netzwerkprogrammierung 
tummeln, so schließe ich meine Frage gleich zum Thema lwip an, 
vielleicht kann mir ja hier jemand helfen :)

Ich habe bereits ein bestehendes Programm, mit dem ich durch einen 
ATWINC1500 "callbacks" von socket, recv, send, accept, bind 
erhalte...Nun würde ich gern das selbe mit einem ESP32 ohne den 
ATWINC1500 realisieren: also lwip & callbacks..

Ist sowas leicht realisierbar?

Danke & schönen Abend :)

von Chris (Gast)


Lesenswert?

Alexander M. schrieb:
> also lwip & callbacks

Du hast beide Antworten in deinem Thread schon bekommen? Entweder nimmst 
du FreeRTOS mit Callbacks oder gehst Socket mit Select (und rufst deine 
Callbacks selber auf).

von Alexander M. (a_lexander)


Lesenswert?

Ja schon, nur bin ich noch nicht ganz glücklich mit der aktuellen 
Antwort ..
1. Ich würde gern in EINEM Task als Server mehrere Services abarbeiten 
--> FreeRTOS würde in diesem Fall ausscheiden
2. Timeout mit "select" geht meiner Meinung nach nur mit recv / send --> 
ich benötige accept aber auch noch ...

Mir erscheint das so in meinem Fall nicht realisierbar...

Vielleicht noch eine Zusatzfrage:
Wo gibt es denn gute Dokumentation über lwip, gibt's sowas überhaupt, 
oder gilt lwip zum Themengebiet Socketprogrammierung / 
Netzwerkprogrammierung und lehnt sich an deren (sehr zahlreichen) 
Dokumentationen an?

: Bearbeitet durch User
von Andreas M. (amesser)


Lesenswert?

Auf einem Posix-System geht "select" natürlich auch für den server 
socket für "accept". Dafür nimmt man den server socket in die read_fds 
rein. Sobald eine Verbindung eingegangen ist, kommt select dann mit 
POLL_IN für den server socket zurück. (Bezeichnungen evtl. falsch, ich 
benutze meistens poll())

Ich weis jetzt nicht wie die Unterstützung beim Lwip dafür ist. Ich 
scheue mich allerdings davor den ganzen overhead der posix api im 
embedded Bereich mitzunehmen, wir benutzen immer die callbacks.

Alexander M. schrieb:
> 1. Ich würde gern in EINEM Task als Server mehrere Services abarbeiten
> --> FreeRTOS würde in diesem Fall ausscheiden

Das verstehe ich nicht. Wenn man schon ein FreeRTOS im Einsatz hat, kann 
man natürlich den ganzen Lwip plus die Server in einer einzigen Task 
laufen lassen. Z.b. wenn man die Callbacks nutzt. Außerdem kann man den 
Lwip Prozess auch mitbenutzen indem man Timeout callbacks startet.

Wenn man gar kein RTOS hat, dann kann, bzw. muss man eine bestimmte 
Funktion (Namen hab ich vergessen) regelmäßig aus seiner Hauptschleife 
raus aufrufen. Ist auch kein Hexenwerk.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Alexander M. schrieb:
> Ja schon, nur bin ich noch nicht ganz glücklich mit der aktuellen
> Antwort ..
> 1. Ich würde gern in EINEM Task als Server mehrere Services abarbeiten
> --> FreeRTOS würde in diesem Fall ausscheiden

Das ist eine etwas unklare Aussage. Meinst Du damit, dass Du am liebsten 
in mehreren threads derselben Task nebenläufig Anfragen an der Server 
abarbeiten würdest und FreeRTOS deswegen ausscheidet, weil es nur 
multitasking, aber kein multithreading unterstützt?

Du könntest mal gucken, ob FreeRTOS Coroutinen Dir dabei weiter helfen. 
Andernfalls müsstest Du natürlich auf ein echtes Multithreading OS 
umsteigen, aber was da in Frage kommt, hat schon einen wesentlich 
höheren Grundfootprint als FreeRTOS. Einen Tod muss man sterben...

Die beste Architektur zum nebenläufigen Abarbeiten von mehreren offenen 
I/O operationen sind I/O completion ports. Die gibt es aber meines 
Wissens nach nur unter einem Betriebssystem, das hier eher unpopulär 
ist...

von Alexander M. (a_lexander)


Lesenswert?

Hallo Zusammen.

Leider hab ich die letzten beiden Artikel nicht ganz verstanden, 
vielleicht ist das aber tatsächlich nah mit der Lösung meines Problems 
verbunden.
Deshalb würde ich gern nochmals mein Problem "genauer" schildern:

Ich habe 3 Tasks:
HTTP, HTTPS:
Hier sollen "Anweisungen" an den CONTROL task erfolgen, z.B. "Erstelle 
ein Socket", "Empfange Daten", "Sende Daten"

CONTROL:
Wartet auf Anweisungen (Pollt), und bei einer "eingegangen" Anweisung 
(z.B. "accept"-Anweisung vom HTTP Task) erfolgt im CONTROL task eine 
Abarbeitung dieser Anweisung:

accept(socket_id, socket_address, socket_length);

In diesem Fall würde aber jetzt der CONTROL Task solange warten, bis 
das accept vom http-service erfolgreich war (blocking). Eine 
"gleichzeitige" Abarbeitung der Anweisungen ist somit nicht möglich.

Mein Ziel ist es aber, die Service HTTP & HTTPS gleichzeitig zum laufen 
zu bekommen.
Mit einer Art "callbacks" würde ich dieses Problem umgehen, z. B.:
accept(socket_id, socket_address, socket_length, CALLBACK);

Leider gibt's sowas aber meiner Meinung nach nicht...

Grüße & schönen Abend :)

von Johannes S. (Gast)


Lesenswert?

das Konzept hört sich komisch an. HTTP und HTTPS sind doch zwei 
verschiedene Ports und da macht man zwei Threads mit je einem eigenen 
socket und accept(). Wenn accept einen client socket liefert kann der an 
einen oder mehrere Workerthreads übergeben werden, diese müsste man für 
HTTP und HTTPS gleich machen können.

von Alexander M. (a_lexander)


Lesenswert?

Ja, da geb ich dir schon Recht, das klingt auf dem ersten Blick nicht 
ganz überzeugend, die Frage ist aber trotzdem:

Wäre diese Abarbeitung irgendwie möglich, z.B. durch Implementierung 
eines Callbacks?

Grüße :)

von Johannes S. (Gast)


Lesenswert?

So prinzipiell ja. In Mbed gibt es ein socket.sigio wo ein callback auf 
socket events registriert werden kann:
https://os.mbed.com/docs/mbed-os/v5.15/mbed-os-api-doxy/class_socket.html#a59ccc5950bb6b32b712444b2a92c0631

Mbed benutzt auch den Lwip Stack, wenn man sich durch die Quellen wühlt 
müsste man den Ansatzpunkt finden.
Laut Doku ist das aber dünnes Eis. Da müsste man mit einer MessageQueue 
arbeiten die auch aus einer ISR gefüttert werden darf. Wollte ich auch 
schon mal machen, ein Webserver mit vielen threads ist sehr 
Ressourcenhungrig.

von Alexander M. (a_lexander)


Lesenswert?

Danke für die Info, aber ich glaube, das ist mir aktuell dann doch ne 
Nummer zu hoch. Schade, dass es keine "einfache" Lösung gibt... :D

Grund der Frage ist Folgende:

Ich habe eine bereits funktionierende Implementierung mit einem 
ATWINC1500, der über ein Bussystem "Events" (accept, send, ...) an den 
Haupt-uC sendet. Dieser arbeitet dann diese Events in einem Callback ab.

Eine ähnliche "Callback"-Funktionalität hätte ich gern auf meinem 
ESP32...
Doku:
https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/lwip.html

Grüße :)

: Bearbeitet durch User
von fghghjr56zrthftrrrzrtrtzrtzrtzrtzrtzrtzr (Gast)


Lesenswert?

socket port 80 im task 1
socket port 443 im task 2
deine  control geschichte im task 3


scheinbar hast du noch nicht ganz verstanden wie das mit den RTOs tasks 
funktioniert

es ist der sinn dahinter das eine funktion blockieren kann und das auch 
teilweise SOLL ( -> idle 0% auslastung wenn nichts passiert )

du musst deine sockets erstmal grundsätzlich so schreiben das sie das 
tun was sie sollen ..
Daten empfangen und das was du benötigst an den control task 
weiterleiten
( hier werfe ich mal mailbox in die runde )

im prinzip wartet dein controltask auf einen eingang der mailbox
dieser Task wacht endlich mal auf und arbeitet die Daten ab

wenn du nur eine einzige info hast für einen Taks um den aufzuwecken
( zB DMA transfer abgeschlossen -> tu was mit daten )  reicht eine 
Tasknotification aus .


wenn du aus merhreren tasks was abarbeiten sollst ( statemaschine ) 
dann mailbox


diese mailbox fütterst du mit den daten für deinen controltask
Der http oder https arbeiten indessen weiter eingehende daten ab
( priorisierung )


Ist der controltask mal an der reihe arbeitet er die daten ab und legt 
sich wieder schlafen

von fghghjr56zrthftrrrzrtrtzrtzrtzrtzrtzrtzr (Gast)


Lesenswert?

prinzig des RTOs ist dann nur den task aufzuwecken ( wenn auch nur für 
eine runde in der loop ) wenn etwas passiert

sonst liegen alle tasks im dauerschlaf




was auch funktioniert ist eine zykische unterbrechung
1
event_t myEvent;
2
3
while(1)
4
{
5
   if( xQueueReceive( queue , &myEvent, 100  ) == true )
6
   {  
7
     // tu was bei events aus der mailbox 
8
   } else {
9
10
     // da die warte funktion der mailbox alle 100ms 
11
     // aufwacht kann man hier zyklische sachen machen 
12
   }
13
14
}

von Alexander M. (a_lexander)


Lesenswert?

Danke für den interessanten Input :)

Ich würde aber trotzdem (und dieses Mal zum allerletzten Mal) nochmals 
die Frage in die Runde stellen, ob die Abarbeitung verschiedener 
Services in 1 Task wirklich nicht möglich ist? Ob sinnvoll oder nicht 
ist erstmal ein anderes Thema...

Also:
Kann ich 2 Services (HTTP + HTTPS) über 1 Task laufen lassen, ist das 
theoretisch möglich mit der lwip library? Wenn ja, bitte bitte mit 
kleinem Gedankenbeispiel aufzeigen...

Danke :)

von Stefan F. (Gast)


Lesenswert?

Alexander M. schrieb:
> Wo gibt es denn gute Dokumentation über lwip

https://en.wikipedia.org/wiki/LwIP#External_links

von Alexander M. (a_lexander)


Lesenswert?

Danke :)

Auf die Wiki Seite bin ich natürlich nicht gekommen, ob man..
Durchaus gute Dokumentation dabei. Das werde ich mir morgen anschauen ;)

Grüße

von Stefan F. (Gast)


Lesenswert?

Alexander M. schrieb:
> Auf die Wiki Seite bin ich natürlich nicht gekommen

Da leitet dich der Autor von lwIP (Adam Dunkels) hin, wenn du auf seine 
Homepage gehst. Hast du wirklich gesucht?

von Alexander M. (a_lexander)


Lesenswert?

Ungelogen: Ja

von fghghjr56zrthftrrrzrtrtzrtzrtzrtzrtzrtzr (Gast)


Lesenswert?

Hmm

Grundsätzlich wäre das doof ...

Wenn man 2 Abarbeitungen hätte wäre der Overhead da zu filtern wo es Her 
kam
Oder das Accept wo gerade nichts kommt blockiert die Abarbeitung 
teilweise
Das select muss auch gepollt werden und würde ggf etwas Zeit in Anspruch 
nehmen.



Ich habe das bei einem UDP Service laufen weil es nur entweder von A 
oder von B kommen kann
Aber nie von beiden.
Das ist aber ein Spezialfall !!!

Bei http oder https .. würde ich immer getrennte Tasks laufen lassen.

Die payload gut vorfiltern und das dann dem Controltask per Mailbox 
überreichen

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Alexander M. schrieb:
> Danke für den interessanten Input :)
>
> Ich würde aber trotzdem (und dieses Mal zum allerletzten Mal) nochmals
> die Frage in die Runde stellen, ob die Abarbeitung verschiedener
> Services in 1 Task wirklich nicht möglich ist? Ob sinnvoll oder nicht
> ist erstmal ein anderes Thema...
>
> Also:
> Kann ich 2 Services (HTTP + HTTPS) über 1 Task laufen lassen, ist das
> theoretisch möglich mit der lwip library? Wenn ja, bitte bitte mit
> kleinem Gedankenbeispiel aufzeigen...
>
> Danke :)

Nochmal:

Bist Du mit dem Kontrollflussmodell in TCP (oder in deinem Fall auf 
höherer Schicht HTTP(s)) Netzwerkkommunikationen vertraut? Speziell, 
bist Du Dir darüber bewusst, dass jede Instanz einer serverseitigen 
Kommunikation (in deiner Terminologie "Services") applikationsseitig 
einen Verbindungsversuch eines clients explizit annehmen ("accept") und 
diesen dann sequentiell (d.h. Empfang des GET requests folgend von der 
Antwort über derselben logischen TCP Verbindung) abarbeiten muss?

Wenn ja, solltest Du die Frage eigentlich selber beantworten können. Es 
geht genau dann, wenn Du in deiner einen task selber einen Grad von 
Nebenläufigkeit zwischen den beiden Kommunikationsinstanzen erzeugen 
kannst, d.h. die beiden Instanzen so wechselseitig abarbeiten kannst, 
dass die jeweiligen clients in einer akzeptablen Zeit eine Antwort auf 
ihre requests kriegen. Das geht z.B. wie von dem/der KollegIn mit dem 
langen Namen geschildert wenn Du eine eigene message pump mit 
Zustandsautomaten für jede Verbindung baust. Allerdings kann/wird es 
dann immer noch passieren, dass eine längere CPU Auslastung (z.B. bei 
der Berechnung der Antwort auf einen GET request) den anderen Service 
unvorhersehbar und/oder unakzeptabel lange ausbremst.

Warum genau willst Du auf das Multitasking verzichten?

P.S. Wenn ich so deine Ausführungen lese, scheinst Du so ein bisschen 
ein unklares Verhältnis von services zu haben. In Kontrollflussbegriffen 
sind nicht HTTP und HTTPS verschiedene entities, sondern a) der 
thread/task, der eine Verbindung annimmt und b) der sie abarbeitet, 
jetzt mal unabhängig, ob das eine HTTP, FTP, POP oder welche immer 
andere Nutzdatenkommunikation das ist. In Unix/Linux gibt es da z.B. den 
Inet Dämon, der für die Annahme von Verbindungen und deren Weiterleitung 
an protokollspezifischen tasks verantwortlich ist, und die Prozesse, die 
dann die so entgegengenommenen Verbindungen abarbeiten.

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

Mit einem Posix like API sollte das gehen wenn die sockets non blocking 
gesetzt werden. Dann muss aber mit polling gearbeitet werden wenn das OS 
keine callbacks vorgesehen hat, und das war ja die Frage.
Das MT bringt evtl. einen Vorteil indem  es die Rechenzeit gerechter 
verteilt, kostet dafür mehr Speicher für den Stack der per Thread 
gebraucht wird.

von Stefan F. (Gast)


Lesenswert?

Ruediger A. schrieb:
> In Unix/Linux gibt es da z.B. den
> Inet Dämon, der für die Annahme von Verbindungen und deren Weiterleitung
> an protokollspezifischen tasks verantwortlich ist, und die Prozesse, die
> dann die so entgegengenommenen Verbindungen abarbeiten.

Wobei man ehrlicherweise ergänzen sollte, dass der inet Dämon ein 
historisches Relikt ist, das in der Praxis kaum noch benutzt wird. Die 
meisten Anwendungen, erledigen dessen Aufgaben inzwischen lieber selber.

Nichts desto Trotz funktioniert der inet Dämon natürlich immer noch und 
ist ein einfaches Hilfsmittel, um mal eben schnell einen Webservice auf 
basis simpler Eingabe/Ausgabe auf stdin/stdout zu basteln, ohne sich mit 
Netzwerk-Bibliotheken auseinander zu setzen.

von Alexander M. (a_lexander)


Lesenswert?

Danke euch ;)

Ja da gibt es noch einiges an Grundverständnis zu lernen...
Vermutlich werde ich dann doch, wie bisher zum großen Teil 
vorgeschlagen, die Instanzen der serverseitigen Kommunikation über 
mehrere Tasks laufen lassen:

Beispiel:
HTTP-task mit accept, recv, ...
HTTPS-task mit accept, recv, ...
...

Vielleicht eine kurze Bestätigung, ob das so vorgeschlagen wurde, damit 
ich das wirklich richtig verstanden habe.

Danke & Schönen Abend :)

von S. R. (svenska)


Lesenswert?

Du kannst auch drei Tasks machen: HTTP, HTTPS und die eigentliche Logik.

Der HTTP-Task kümmert sich um accept, recv und send für 
unverschlüsselten Kram. Der HTTPS-Task kümmert sich um accept, recv und 
send für verschlüsselten Kram. Der dritte Task bekommt von den beiden 
anderen Tasks die Requests, parst die und schickt die Antwort an den 
jeweiligen Task zurück.

Damit kannst du die eigentliche Programmlogik vom verwendeten Protokoll 
trennen und es ist egal, ob die Anfrage nach "/super.txt" nun über HTTP 
oder HTTPS reinkam.

: Bearbeitet durch User
von Ruediger A. (Firma: keine) (rac)


Lesenswert?

S. R. schrieb:
> Du kannst auch drei Tasks machen: HTTP, HTTPS und die eigentliche Logik.
>
> Der HTTP-Task kümmert sich um accept, recv und send für
> unverschlüsselten Kram. Der HTTPS-Task kümmert sich um accept, recv und
> send für verschlüsselten Kram. Der dritte Task bekommt von den beiden
> anderen Tasks die Requests, parst die und schickt die Antwort an den
> jeweiligen Task zurück.
>
> Damit kannst du die eigentliche Programmlogik vom verwendeten Protokoll
> trennen und es ist egal, ob die Anfrage nach "/super.txt" nun über HTTP
> oder HTTPS reinkam.

Im Zusammenhang mit non blocking accepts wäre das denkbar und würde so 
eine Art IO Completion Ports für arme Leute implementieren (allerdings 
nur mit einem worker thread für die Arbeit zwischen GET und RESPONSE und 
funktionsgebundenen threads). Nur:

Jede client transaction ist nach wie vor durch die strikt lineare 
Sequenz accept->recv GET->parse GET->compute answer->generate 
RESPONSE->send response bestimmt, d.h. die "worker threads" (in deinem 
Fall der eine) kann Convoyeffekte erzeugen, bei denen der Rundumlauf 
jedes clients von dem der anderen clients abhängen.

Dieser Flaschenhals liesse sich vermeiden, indem man für die "worker" 
einen thread pool beiseite stellt, so dass mit jeder request Abarbeitung 
ein freier thread aus dem pool aufgeweckt wird. Wenn man das Ganze so 
generisch macht, dass so ein worker thread wahlweise IO von TCP oder 
worker computations abarbeiten kann, hat man vollständige IO Completion 
ports (die beste Art und Weise, Server zu implemntieren).

Ausserdem (mal unabhängig von den verschiedenen Lösungsvorschlägen): Es 
wird hier immer impliziert, dass der Unterschied zwischen HTTP und HTTPS 
nur ein kleines detail ist so wie zwischen dem Umschalten einer UI mit 
rotem und blauen Hintergrund. Das stimmt so nicht. HPPTS ist für die 
meisten Embedded Systeme nicht realistisch implementierbar. Das liegt im 
Footprint (SSL ist nicht nur ein Protokoll, sondern eine 
Protokollhierarchie), in der Rechenkapazität (man braucht asymmetrische 
Verschlüsselung, was für kleinere Prozessoren ohne Cryptocoprozessoren 
extrem rechenzeitintensiv ist) und Infrastruktur (für PKA ist ein Zugang 
zu den Certificate trust chains nötig, was wieder einen Riesenschwanz an 
Code nach sich zieht). Wer so etwas will, ist mit einem RTOS auf Cortex 
M in aller Regel schnell am Ende und wird auf einen Cortex A mit 
Embedded Linux oder kompatibel umsteigen müssen.

von S. R. (svenska)


Lesenswert?

Ruediger A. schrieb:
> Ausserdem (mal unabhängig von den verschiedenen Lösungsvorschlägen): Es
> wird hier immer impliziert, dass der Unterschied zwischen HTTP und HTTPS
> nur ein kleines detail ist so wie zwischen dem Umschalten einer UI mit
> rotem und blauen Hintergrund.

Der Unterschied ist nur das kleine Detail, dass zwischen den ein- und 
ausgehenden TCP-Daten und der verarbeitenden Schicht ein Krypto-Layer 
sitzt.

Wichtig ist der Grund, aus dem man HTTPS bzw. Verschlüsselung haben will 
(oder muss). Sicherheit ist nicht immer das relevante Kriterium. Die 
Entwicklung geht dahin, unverschlüsselte Verbindungen aus Prinzip 
abzulehnen - also muss das System verschlüsseln können.

Wenn man damit leben kann, dass der kleine Prozessor pro Anfrage 
vielleicht eine Sekunde Bedenkzeit braucht - was für eine Statusseite 
einer Steuerung völlig ausreicht - dann braucht man da nicht mit einem 
Quadcore draufschießen. Und wenn die Anforderungen steigen, dann wird 
man auch in den mittelgroßen Chips ein bisschen AES-Silizium sehen.

von Johannes S. (Gast)


Lesenswert?

Die TLS Implementierung in Mbed (für Cortex-M) gibt es schon lange und 
auch der ESP nutzt die mbedTLS. Es geht also durchaus, ist nur auch 
speicherhungrig. Und darum würde m.M.n. eine Eventbasierte vs. 
Threadbasierte Lösung Sinn machen. So ist auch der Ansatz in Nodejs um 
das C10k Problem zu eliminieren. Der µC wird natürlich nicht solche 
Anforderungen erfüllen müssen, aber Events statts threads haben ihren 
Charme.

von fgd (Gast)


Lesenswert?

Ruediger A. schrieb:
> Ausserdem (mal unabhängig von den verschiedenen Lösungsvorschlägen): Es
> wird hier immer impliziert, dass der Unterschied zwischen HTTP und HTTPS
> nur ein kleines detail ist so wie zwischen dem Umschalten einer UI mit
> rotem und blauen Hintergrund. Das stimmt so nicht. HPPTS ist für die
> meisten Embedded Systeme nicht realistisch implementierbar. Das liegt im
> Footprint (SSL ist nicht nur ein Protokoll, sondern eine
> Protokollhierarchie), in der Rechenkapazität (man braucht asymmetrische
> Verschlüsselung, was für kleinere Prozessoren ohne Cryptocoprozessoren
> extrem rechenzeitintensiv ist) und Infrastruktur (für PKA ist ein Zugang
> zu den Certificate trust chains nötig, was wieder einen Riesenschwanz an
> Code nach sich zieht). Wer so etwas will, ist mit einem RTOS auf Cortex
> M in aller Regel schnell am Ende und wird auf einen Cortex A mit
> Embedded Linux oder kompatibel umsteigen müssen.

ist auch ein irrglaube

ich habe einen M7 mit 14-20 tasks am laufen inkl dynamischerm Speicher

Das ding macht vieles... inkl einer TLS verbindung ( dynamisch auf/abbau 
)
Das läuft eher als hintergundrauschen.
Habe aus Sicherheitsbedenken alles unter TLS 1.2 deaktiviert.
Damit bleiben nur schlüssel >=256bit und alle halbwegs brauchbaren 
verfahren übrig.


Ja der handshake dauert vlt 200-300ms ...
Die Daten selbst sind kein Problem.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

fgd schrieb:
>
> ich habe einen M7 mit 14-20 tasks am laufen inkl dynamischerm Speicher
>
> Das ding macht vieles... inkl einer TLS verbindung ( dynamisch auf/abbau
> )
> Das läuft eher als hintergundrauschen.
> Habe aus Sicherheitsbedenken alles unter TLS 1.2 deaktiviert.
> Damit bleiben nur schlüssel >=256bit und alle halbwegs brauchbaren
> verfahren übrig.
>
>
> Ja der handshake dauert vlt 200-300ms ...
> Die Daten selbst sind kein Problem.

Welcher POD ist das? Vermutlich mit Crypto Coprozessor?

Auf einem STM32F429 dauert ein asymmetrischer Handshake >>10 Sekunden, 
ich bin für jeden Tipp dankbar, das schneller zu kriegen... Danke!

von fgd (Gast)


Lesenswert?

ist ein STM32F745
rtos + lwip + polarssl ( mbedtls vorgänger )

nein keine cryptoHW.
nur der randomgenerator

socket API + websocket

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.