É importante observar que a programação do kernel difere significativamente da programação do espaço do usuário. O kernel é uma entidade independente, que não pode usar bibliotecas de espaço de usuário, mesmo libc em Linux ou kernel32.dll em Janelas. Como resultado, as funções usuais usadas no espaço do usuário (printf, malloc, free, open, read, write, memcpy, strcpy, etc.) não podem mais ser usadas.
Em conclusão, a programação do kernel é baseada em uma API completamente nova e independente que não está relacionada à API no userspace, quer nos refiramos a POSIX , Win32 ou ANSI C. Uma diferença importante na programação do kernel é como acessá-lo. e alocação de memória. Devido ao fato de que a programação do kernel é feita em um nível muito próximo da máquina física, existem regras importantes em relação ao gerenciamento de memória.
Primeiro, ele trabalha com vários tipos de memória:
– memória física
– memória virtual no espaço de endereço do kernel
– memória virtual do espaço de endereçamento de um processo
– memória residente – sabemos com certeza que as páginas acessadas estão presentes na memória física
A memória virtual no espaço de endereçamento de um processo não pode ser considerada residente devido aos mecanismos de memória virtual implementados pelo sistema operacional: as páginas podem estar no swap, ou podem não estar presentes na memória física como resultado do mecanismo de paginação de solicitação.
A memória no espaço de endereço do kernel pode ou não ser residente. Ambos os dados e segmentos de código de um módulo e a pilha de kernel de um processo são residentes.
Amemória dinâmica pode ou não ser residente, dependendo de como é alocada. Ao trabalhar com memória residente, as coisas são simples: a memória pode ser acessada a qualquer momento. No entanto, se estiver trabalhando com memória não residente, ela só poderá ser acessada em determinados contextos.
Amemória não residente só pode ser acessada a partir do contexto do processo. Acessar a memória não residente a partir do contexto de interrupção tem resultados imprevisíveis e, portanto, quando o sistema operacional detecta tal acesso, tomará medidas drásticas: bloquear ou reinicializar o sistema, para evitar danos graves.
A memória virtual de um processo não pode ser acessada diretamente do kernel. Geralmente, é totalmente desencorajado acessar o espaço de endereçamento de um processo, mas há situações em que um driver de dispositivo precisa fazer isso. O caso típico é quando o driver de dispositivo precisa acessar um buffer do espaço do usuário. Nesse caso, o driver de dispositivo deve usar funções especiais e não acessar o buffer diretamente. Isso é necessário para evitar o acesso a áreas de memória inválidas.
Outra diferença da programação em userspace, em relação ao trabalho com memória, é devido à pilha, a pilha cujo tamanho é fixo e limitado. No kernel Linux, uma pilha de 4K é usada por padrão e, no Windows, uma pilha de 12K é usada. Por esse motivo, a alocação de grandes estruturas na pilha ou o uso de chamadas recursivas devem ser evitados.
Em relação ao modo de execução no kernel, distinguimos dois contextos: contexto de processo e contexto de interrupção. Estamos no contexto do processo quando estamos executando o código seguindo uma chamada do sistema ou quando estamos executando no contexto de uma thread do kernel. Quando estamos executando a rotina de lidar com uma pausa ou uma ação adiada, estamos executando em um contexto de pausa.
Uma das características mais importantes da programação do kernel é o paralelismo. Tanto o Linux quanto o Windows oferecem suporte a sistemas SMP com vários processadores, mas também o kernel oferece suporte preventivo a vários processadores. Isso torna a programação do kernel mais difícil porque o acesso a variáveis globais deve ser sincronizado com spinlock ou primitivas de bloqueio.
Tanto o Linux quanto o Windows usam kernels preemptivos. A noção de multitarefa preemptiva não deve ser confundida com a noção de kernel preemptivo. A noção de multitarefa preemptiva refere-se ao fato de que o sistema operacional interrompe a execução de um processo de forma forçada, quando expira o tempo e é executado no espaço do usuário, para executar outro processo.
Para programação no Kernel Linux, a convenção usada para chamar funções para indicar sucesso é idêntica àquela na programação UNIX: 0 para sucesso ou um valor diferente de 0 para falha.