Resumo

  • A RFC 817 distinguiu a camada especificada do módulo executado. O contrato externo de IP e TCP precisava continuar interoperável, mas a divisão interna podia seguir desmultiplexação, temporizadores, propriedade do estado e custo de escalonamento.
  • Isolamento facilita substituição e reúso, porém pode cobrar em pacotes, cópias e trocas de contexto. Um sinal limitado podia reunir ACK, janela e eco Telnet sem dar à aplicação autoridade sobre o transporte.
  • A otimização aceitável começava na medição do caminho comum. Código específico para um gargalo comprovado, filas voltadas à operação mais frequente e menos cópias eram defensáveis; uma abstração bonita ou um caso raro não eram prova.

Cada travessia movia mais do que bytes

Uma implementação dividida em processos podia parecer modular: IP no kernel, TCP em um processo separado, aplicações acima dele. Mas um datagrama recebido ainda não revelava seu destino final até que alguém examinasse o cabeçalho TCP. O sistema acordava o processo TCP para identificar a conexão e depois acordava o processo da aplicação. Uma decisão de desmultiplexação exigia duas passagens de execução.

Se os processos não compartilhassem memória, a separação acrescentava cópias pelo kernel. Se uma interface aceitasse apenas um fluxo de bytes, um programa de transferência em blocos teria de adaptar seu trabalho ao formato que a camada escolheu expor. A fronteira protegia os dois lados, mas cada proteção podia gerar movimento de dados, mudanças de contexto, consultas e alocações.

A RFC 817 propôs cortar o software por função. O kernel poderia executar a parte de IP e TCP necessária para identificar diretamente o processo de destino. O restante do processamento, inclusive ações que precisassem de temporizadores ou de um ambiente capaz de bloquear, continuaria naquele processo. A linha de execução atravessava duas camadas do protocolo porque a função cara — saber quem deveria receber o datagrama — estava no meio delas.

Nada disso mudava o TCP observado pelo par remoto. A especificação continuava determinando os bits e as obrigações na rede. O arranjo local apenas decidia onde o estado morava, em qual contexto o código rodava e quantas vezes os dados precisavam atravessar uma fronteira.

Processo, kernel e máquina auxiliar deslocavam o problema

Colocar o protocolo em um processo permitia trabalhar sem tantas alterações no kernel e oferecia um ambiente natural para espera e temporizadores. Em compensação, cada pacote podia esperar pelo escalonador. Serviços que precisavam se parecer com terminais ou dispositivos locais às vezes voltavam ao kernel por uma interface tortuosa, devolvendo ao caminho a complexidade que a organização tentara retirar.

No kernel, o protocolo evitava uma troca de processo e se integrava aos dispositivos. O preço surgia nas ações complexas acionadas por temporizador. Código executado em nível de interrupção normalmente não podia bloquear, podia mascarar interrupções por tempo excessivo e, sob uma chegada intensa de pacotes, podia consumir a máquina sem que o escalonador o limitasse. Espaço escasso no kernel e mudanças no sistema operacional também tornavam a manutenção mais cara.

Um processador de comunicações separado prometia um sistema feito para protocolos e reutilizável entre hosts. Ainda assim, host e processador precisavam conversar. Essa interface tinha enquadramento, fluxo, notificação, falha e ordem: era outro protocolo. A fronteira física mudava de lugar, mas sua disciplina e sua superfície de falha permaneciam.

A escolha responsável, portanto, não era uma preferência abstrata por kernel ou processo. Era uma decomposição das ações: proximidade do dispositivo, desmultiplexação, espera, temporizadores, estado por conexão, cópia, recuperação e custo de cada cruzamento.

Um caractere podia produzir três respostas corretas

No Telnet em modo caractere, a chegada de uma tecla criava obrigações independentes. TCP precisava confirmar o segmento. A janela de recepção podia mudar. Telnet ou a aplicação precisava ecoar o caractere, e um comando de controle podia acrescentar mais trabalho. Se cada componente enviasse assim que sua própria resposta ficasse pronta, um único gesto produziria vários pacotes.

Não havia violação do protocolo. Faltava apenas conhecimento sobre o trabalho iminente do vizinho. Cada pacote adicional repetia custo de interrupção, escalonamento e processamento nos dois hosts, além do preço da comunicação em redes tarifadas.

O ajuste sugerido era pequeno: TCP podia saber se a camada superior provavelmente teria dados para enviar e esperar alguns milissegundos antes do ACK. Em Telnet interativo, confirmação, janela e eco poderiam compartilhar um segmento. Em uma transferência unidirecional de arquivo, a mesma espera atrasaria o próximo envio e prejudicaria a vazão. A utilidade do sinal dependia da classe de tráfego.

A RFC 1122 transformou depois esse exemplo em orientação delimitada de delayed ACK: o atraso não deveria superar meio segundo, e pelo menos cada segundo segmento de tamanho completo deveria ser confirmado. O texto repetiu a redução de três segmentos para um no terminal, mas advertiu que demora excessiva afetava medição de RTT e o relógio de pacotes. A intenção da aplicação ajudava a escolher o momento; não suspendia indefinidamente a obrigação do transporte.

O isolamento tinha valor e cobrava aluguel

Uma interface estável permite modificar um lado sem obrigar o outro a conhecer sua implementação. Vários clientes podem usar o mesmo serviço, equipes conseguem trabalhar com menos contexto e diferentes máquinas continuam interoperando. Era essa independência, não uma estética do diagrama, que tornava a modularidade valiosa.

Ao mesmo tempo, a interface escondia intenção. Camadas separadas podiam enviar individualmente informações que caberiam no mesmo pacote. Um limite de processo podia impor cópia. Um serviço genérico podia obrigar um caso de alto volume a executar operações desenhadas para outra carga.

A RFC 817 tratou a fronteira como benefício e penalidade. Não era licença para acoplamento irrestrito. O aluguel deveria ser visível: pacotes por ação útil, cópias, despertares, buscas, atraso, variantes de implementação e custo de correção. Só valia atravessar quando o trabalho poupado fosse real, a informação compartilhada fosse mínima e a ausência do atalho mantivesse o caminho correto.

A RFC 1958 mais tarde colocou modularidade ao lado de desempenho e custo, e deu preferência ao retorno de implementações em funcionamento sobre máximas arquiteturais. A RFC 3439 relacionou complexidade a escala e despesa operacional. As duas continham os extremos: pureza não é gratuita, e acoplamento não é sinônimo de eficiência.

Multics autorizou uma exceção localizada

As medições no Multics deram números ao argumento. Os bytes de oito bits de um segmento TCP se acomodavam de forma inconveniente em palavras de 36 bits. Uma versão inicial do checksum consumia cerca de seis milissegundos para 576 bytes. Uma reescrita cuidadosa e específica da máquina reduziu o tempo para menos de um milissegundo.

A RFC 817 chamou a técnica de suja. Ela era tolerável porque atacava um gargalo extremo, medido e delimitado. A especialização podia ficar presa a uma função, e seu resultado ainda podia ser comparado com um caminho de referência. Não se transformava uma exceção demonstrada em estilo para todo o sistema.

Outras decisões eram menos vistosas. Como o próximo segmento quase sempre chegava em ordem, o teste esperado deveria vir primeiro. A fila de retransmissão era usada com mais frequência para remover entradas já confirmadas do que para retransmiti-las, portanto sua estrutura deveria favorecer a remoção. Dados não deveriam ser copiados de novo só para satisfazer a conveniência de um módulo.

O desempenho se perdia em pequenas parcelas: checksum, cópia, busca, fila, escalonamento e transição de estado. Raramente existia um único monstro cuja eliminação resolvesse o protocolo. Era preciso observar o caminho inteiro antes que o mapa de módulos se tornasse rígido.

O contrato era público; o corte, local

A pilha especificada respondia o que os pares haviam concordado em trocar. O mapa de implementação respondia onde um host gastava tempo, movia bytes, agendava trabalho e continha falhas. Confundir os dois transformava uma explicação de serviços em uma arquitetura de execução não examinada.

A RFC 817 devolveu a decisão às evidências. Contar pacotes, rastrear cópias, separar o tráfego habitual das exceções e registrar qual sinal cruzava a fronteira, quem o possuía, quando expirava e o que ocorria sem ele. O contrato externo permanecia estável para que outra máquina pudesse escolher outro arranjo interno.

A camada era real porque sustentava a interoperabilidade. Seu custo também era real. O módulo era uma escolha da implementação e, por isso, toda linha que atravessasse a pilha precisava provar o ganho e conservar um caminho de volta.

Fontes