Nas opções de driver do Linux, ou como passei o fim de semana

"Somos preguiçosos e curiosos"




Desta vez, o motivo da postagem foi um artigo de uma boa revista dedicada ao sistema operacional Linux (a seguir denominada L), na qual o "especialista" atraído elogiou o motorista que conectava o LCD à placa Raspbery. Como essas coisas (conexão, não o sistema operacional) se enquadram no escopo de meus interesses profissionais, examinei o artigo com atenção, depois encontrei o texto real do “driver” e fiquei um pouco surpreso ao ver que a TI poderia ser elogiada. Bem, em geral, o nível de especialista pode ser determinado apenas porque ele teimosamente chamou o programa de motorista, apesar do fato de que não é de forma alguma. Parece, e com ele, você nunca sabe o que alguém escreve para si mesmo, mas para publicá-lo em domínio público - "Eu não sabia que era possível".

O fato de o endereço do dispositivo no barramento I2C ter sido definido diretamente no texto do programa e para sua alteração exigir sua recompilação (é bom que nem todo o kernel) fique especialmente satisfeito. A propósito, notei que nos fóruns dedicados a L, a resposta mais popular para qualquer pergunta sobre problemas de software é "reconstruir a versão mais recente do kernel". Essa abordagem parece um pouco estranha para mim, provavelmente, não sei de nada. Mas, no entanto, surgiu a questão de como a parametrização do driver é realmente implementada (dentro, não fora - tudo é simples e claro) em A, a resposta à qual este post é dedicado.

Não é que eu sempre escrevi drivers para L, mas com o processo como um todo, estou familiarizado e o Google confirmou memórias vagas de que há um conjunto de macros que devem ser usadas ao criar o texto de origem do módulo para poder passar parâmetros de operação para ele, por exemplo, o endereço do dispositivo para para o ônibus. No entanto, a mecânica do processo em si não foi descrita em lugar algum. Eu vi o mesmo texto em vários links (a propósito, uma pergunta interessante - por que fazer isso, ou seja, colocar o fragmento de texto de outra pessoa no meu recurso - eu realmente não entendo o significado dessa operação), que descreveu as macros acima. Eu não encontrei uma única menção ao mecanismo para executar a operação, para outro sistema operacional conhecido (Windows) eu precisaria declarar um fato e me limitar a isso, mas uma das vantagens de A é a disponibilidade de textos de origem e a capacidade de encontrar uma resposta para qualquer pergunta sobre sua estrutura interna. o que vamos fazer. Observo imediatamente que tentarei não duplicar as informações que você pode obter de outras fontes, e me limitarei apenas àquelas necessárias para a compreensão do texto.

Mas, antes que você olhe a fonte, primeiro pensaremos um pouco, mas como faríamos se tivéssemos uma tarefa semelhante (e de repente, após este post, eles me convidarão para os mineiros L e você não se recusará). Portanto, existe a possibilidade de criar um módulo - uma determinada unidade de programa especialmente projetada que pode ser carregada na memória para execução usando algum utilitário do sistema (insmode - daqui em diante I), enquanto uma cadeia de caracteres é passada como parâmetros de inicialização. Essa linha pode incluir unidades lexicais estritamente definidas, cuja descrição do formato é especificada ao criar o texto de origem do módulo, e essas unidades contêm informações que permitem alterar o valor das variáveis ​​internas deste módulo.

Vamos considerar com mais cuidado a maneira de descrever as unidades lexicais acima, precisamos disso para considerar várias soluções. A unidade de análise é determinada chamando a macro, que é informada das informações necessárias - o nome da variável que deve ser modificada durante o processo de instalação, seu nome externo (geralmente o mesmo que o anterior), o tipo da variável do conjunto limitado e os direitos de acesso à variável no estilo rw-rw-rw. Além disso, uma sequência de texto (opcional) que descreve a variável pode ser especificada. Obviamente, essas informações são necessárias e suficientes (em conjunto com as regras para o design de unidades sintáticas - separadores e tokens) para construir o analisador da lista de parâmetros especificada na forma de uma sequência de texto, mas deixa espaço para a implementação da distribuição de funções entre o participante do processo.

Para configurar o módulo, precisamos:

  1. formulário (bem, isso está na fase de compilação, você pode fazê-lo como quiser, embora ainda seja interessante como) e armazenar uma tabela com as configurações acima,
  2. analisando os parâmetros de entrada de acordo com esta tabela e
  3. faça alterações em determinadas áreas da memória de acordo com o resultado da análise de uma unidade sintática.

Vamos pensar um pouco no estilo de "se eu fosse o diretor" e apresentar as possíveis implementações. Como poderíamos implementar o comportamento semelhante do utilitário do sistema e do módulo - iniciaremos a análise das opções em crescente complexidade.

A primeira solução é o utilitário And quase nada, apenas chama o módulo indicado e transfere os parâmetros restantes no estilo de linha de comando para ele, e o módulo já os analisa, contando com as informações disponíveis nele e faz as modificações necessárias. Essa solução é simples, compreensível e bastante viável, mas a seguinte circunstância deve ser levada em consideração: de modo algum a análise dos parâmetros deve ser deixada à vontade do autor do módulo, pois isso fornecerá a ele um espaço inaceitável e, afinal, dois programadores sempre escreverão três opções de analisador. E então fomos encontrá-lo, permitindo que parâmetros de um tipo indefinido, que tenham uma string de texto como valor, sejam suficientes para ele.

Portanto, um determinado analisador padrão deve ser incluído automaticamente no texto do módulo, o que é fácil de implementar no nível de substituição de macro.

Esta solução tem duas desvantagens:

  1. não está claro por que precisamos disso. Você pode chamar imediatamente o módulo com parâmetros na linha de comando,
  2. o código do módulo (parte de inicialização) deve conter todas as três seções das informações necessárias, e essas informações são necessárias apenas quando o módulo é iniciado e não é usado no futuro, e sempre ocupa espaço. Imediatamente, faça uma reserva de que essas informações necessariamente ocupam espaço no arquivo, mas elas podem não ficar na memória ao carregar o módulo, se tudo for feito com cuidado. Para fazer exatamente isso, lembramos das diretivas _init e _initdata (a propósito, mas como elas funcionam, teríamos que descobrir isso - este é o tópico do próximo post - você está ansioso por isso?). Porém, no último caso, as seções 2 e 3 das informações no arquivo são claramente redundantes, pois o mesmo código estará presente em muitos módulos, violando maliciosamente o princípio DRY.

Devido às deficiências observadas, a implementação desta opção é altamente improvável. Além disso, não está claro por que, na macro, definir informações sobre o tipo de parâmetro, porque o próprio módulo sabe muito bem o que modifica (embora possa ser necessário para o analisador ao verificar os parâmetros). A avaliação geral da probabilidade de tal decisão é de 2 a 3%.

A digressão necessária sobre o inconveniente observado número 2 - eu fui formado como especialista naqueles dias em que 256 kbytes de RAM eram suficientes para organizar 4 estações de trabalho, 56 kbytes tinham um sistema operacional de duas tarefas e um sistema operacional de uma tarefa começou a trabalhar a 16 kbytes. Bem, 650 kb, o que deveria ser suficiente para qualquer programa, eram geralmente algo do campo da ficção não científica. Portanto, estou acostumado a pensar que a RAM é um recurso escasso e estou extremamente desaprovando seu uso desnecessário, a menos que seja absolutamente necessário (como regra, requisitos de desempenho) e, nesse caso, não observo tal situação. Como a maioria dos meus leitores se formou em diferentes realidades, você pode ter suas próprias avaliações da preferência desta ou daquela opção.

A segunda solução - o analisador em si é transferido para AND, que transfere os dados extraídos para o módulo (sua parte de inicialização) - número e valor do parâmetro. Em seguida, preservamos a uniformidade dos parâmetros e reduzimos os requisitos para o tamanho do módulo. A questão permanece: como fornecer uma lista AND de possíveis parâmetros, mas isso é fornecido pelas macros criando uma estrutura predeterminada do módulo e a localização do bloco em algum local específico (arquivo ou memória). A solução é melhor que a anterior, mas ainda resta excesso de memória no módulo. Em geral, eu gosto da solução, porque meu analisador (que é pior que todos os outros programadores, tenho meu próprio analisador, não sem falhas, mas definitivamente não fatal) funciona de acordo com esse esquema, retornando o número da regra e valor identificados ao programa principal parâmetro No entanto, a probabilidade de implementar esta opção em particular não é muito alta - 5%.

Uma subopção da segunda solução é transferir os parâmetros extraídos não para a parte inicial do módulo, mas diretamente para a parte de trabalho carregada, por exemplo, via ioctl - os requisitos de memória são os mesmos. Temos uma oportunidade única de alterar os parâmetros "on the fly", que não é implementado em outras versões. Não está muito claro por que precisamos desse recurso, mas é bonito. A desvantagem é 1) você precisa reservar uma parte da área de função com antecedência para uma solicitação possivelmente não utilizada e 2) o código modificador deve estar presente na memória constantemente. Estimativa da probabilidade de implementação - porcentagem 5.

A terceira solução é transferir para E também a modificação dos parâmetros. Em seguida, no processo de carregamento do código binário do módulo E, ele pode modificar os dados na memória intermediária e carregar o código do driver com os parâmetros alterados para o local de implantação permanente, ou fazer essas modificações diretamente na área de memória na qual o binário foi carregado e a tabela de parâmetros presente no arquivo está na memória pode carregar e não ocupar (lembre-se das diretrizes). A decisão é responsável, exigirá, como a anterior, a presença de uma área de comunicação predefinida entre o módulo e AND para armazenar a descrição dos parâmetros, mas reduz ainda mais os requisitos para excesso de memória no módulo. Imediatamente, notamos a principal desvantagem dessa solução - a incapacidade de controlar os valores dos parâmetros e sua consistência, mas não há nada a ser feito. É uma solução bastante normal, provavelmente é - 75%.

Uma variante da terceira solução - as informações sobre os parâmetros não são armazenadas no próprio módulo, mas em algum arquivo auxiliar, simplesmente não há excesso de memória no módulo. Em princípio, o mesmo pode ser feito na versão anterior, quando o módulo contém a parte de configuração, que é usada AND durante o processo de inicialização, mas não é carregada na RAM que contém a parte executável real do módulo. Comparado com a versão anterior, um arquivo extra foi adicionado e não está claro pelo que estamos pagando, mas talvez eles tenham feito diretivas de inicialização antes da invenção - 5%.

Os 7% restantes serão deixados para outras opções que não consegui encontrar. Bem, agora que nossa fantasia se esgotou (minha, com certeza, se houver mais idéias, por favor, pergunte no comentário), começaremos a estudar a fonte de L.

Para começar, observo que, aparentemente, a arte de distribuir textos de origem em arquivos foi perdida junto com o sistema operacional, que cabia em 16 kb, uma vez que a estrutura dos diretórios, seus nomes e nomes de arquivos estão conectados ao conteúdo um pouco mais do que nada. Dada a presença de inclusões incorporadas, o estudo clássico de fontes baixadas com a ajuda de um editor se transforma em uma busca estranha e será improdutivo. Felizmente, existe um utilitário Elixir encantador, disponível on-line, que permite realizar pesquisas contextuais, e aqui com ele o processo se torna muito mais interessante e proveitoso. Realizei minhas pesquisas adicionais no site elixir.bootlin.com. Sim, este site não é uma coleção oficial de queijos do kernel, diferente do kernel.org, mas esperamos que o código fonte deles seja idêntico.

Primeiro, vamos olhar para uma macro para determinar parâmetros - primeiro, sabemos o nome e, segundo, deve ser mais fácil (sim, agora). Está localizado no arquivo moduleparam.h - é bastante razoável, mas é uma surpresa agradável, considerando o que veremos mais adiante. Macro

{0}module_param(name,type,perm) 

é um invólucro

  {0a}module_param_named(n,n,t,p) 

- açúcar sintático para o caso mais comum. Ao mesmo tempo, por algum motivo, a enumeração dos valores permitidos de um dos parâmetros, ou seja, o tipo da variável, é fornecida nos comentários antes do texto do wrapper, e não na segunda macro, que realmente faz o trabalho e pode ser usada diretamente.

A macro {0a} contém uma chamada para três macros

 {1}param_check_##t(n,&v) 

(existe um conjunto de macros para todos os tipos válidos)

 {2}module_param_cb(n,&op##t,&v,p) 

e

 {3}__MODULE_PARM_TYPE(n,t) 

(preste atenção aos nomes, no entanto, charme), e o primeiro deles é usado em outros lugares, ou seja, as recomendações de Occam e o princípio do KISS também são negativamente negligenciadas pelos criadores de A - aparentemente, algum tipo de base para o futuro. Claro, estas são apenas macros, mas não custam nada, mas ainda assim ....

A primeira das três macros {1}, como o nome indica, verifica a correspondência dos tipos de parâmetros e envolve

 __param_check(n,p,t) 

Observe que, no primeiro estágio do agrupamento, o nível de abstração da macro diminui e, no segundo, provavelmente aumenta de uma maneira diferente, e me parece que poderia ser mais simples e mais lógico, especialmente considerando que a macro média não é usada em nenhum outro lugar. Ok, vamos colocar outra maneira de verificar os parâmetros da macro no mealheiro e seguir em frente.

Mas as próximas duas macros realmente geram um elemento da tabela de parâmetros. Por que você não pergunta a dois, e não a um, deixo de entender a lógica dos criadores de L. Muito provavelmente, com base na diferença no estilo dessas duas macros, começando pelos nomes, a segunda foi adicionada posteriormente para expandir a funcionalidade e modificar a estrutura existente Era impossível, porque inicialmente eles se arrependeram de alocar um local para indicar uma variante dos parâmetros. A macro {2}, como sempre, mascara a macro de nós

 {2a}_module_param_call(MODULE_PARAM_PREFIX,n,ops,arg,p,-1,0) 

(é engraçado que essa macro não seja chamada diretamente em qualquer lugar, exceto 8250_core.c, onde é chamada com os mesmos parâmetros adicionais), mas a última já produz o código-fonte.

Uma pequena observação - durante a pesquisa, garantimos que a navegação de texto funcione bem, mas há duas circunstâncias desagradáveis: a pesquisa pelo fragmento de nome não funciona (o check_param_ não foi encontrado, embora o check_param_byte tenha sido encontrado) e a pesquisa funciona apenas nas declarações do objeto (a variável não foi encontrada, então é encontrado neste arquivo pelo ctrF, mas a pesquisa interna por fonte não é detectada). Não é muito encorajador, porque podemos precisar procurar um objeto fora do arquivo atual, mas "no final, não temos outro".

Como resultado do trabalho de {1} no texto do módulo compilado na presença das duas linhas a seguir

 module_param_named(name, c, byte, 0x444); module_param_named(name1, i, int, 0x444); 

um fragmento do tipo abaixo aparece

 static const char __param_str_name[] = "MODULE" "." "name"; static struct kernel_param const __param_name \ __attribute__((__used__)) \ __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \ = { __param_str_name, ((struct module *)0), &param_ops_byte, (0x444), -1, 0, { &c } }; static const char __UNIQUE_ID_nametype72[] \ __attribute__((__used__)) __attribute__((section(".modinfo"), unused, aligned(1))) \ = "parmtype" "=" "name" ":" "byte"; static const char __param_str_name1[] = "MODULE" "." "name1"; static struct kernel_param const __param_name1 \ __attribute__((__used__)) \ __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \ = { __param_str_name1, ((struct module *)0), &param_ops_int, (0x444), -1, 0, { &i } }; static const char __UNIQUE_ID_name1type73[] __attribute__((__used__)) \ __attribute__((section(".modinfo"), unused, aligned(1))) \ = "parmtype" "=" "name1" ":" "int"; 

(na verdade, os arquivos de linha única são gerados lá, eu os dividi em linhas para facilitar a revisão) e podemos dizer imediatamente que não há dica da inclusão de uma seção do programa analisador ou de um módulo para atribuir valores a parâmetros no texto de origem, para que as opções 1 e 2 possam ser considerados excluídos de consideração adicional. A presença de atributos especiais para o vinculador, por assim dizer, sugere a existência de uma região de comunicação localizada em algum local predeterminado através do qual a descrição dos parâmetros é transmitida. Ao mesmo tempo, notamos com perplexidade a completa ausência de qualquer descrição do bloco gerado de possíveis parâmetros na forma de texto que pode ser usado pelo módulo analisador. É claro que o código bem escrito é auto-documentado, mas não na mesma extensão que novamente não aumenta a probabilidade da opção 1 ou 2, com o analisador sendo gravado pelo desenvolvedor do módulo.

A combinação dos atributos __used__ e não utilizados ao mesmo tempo parece engraçada na última linha gerada, especialmente se você observar o próximo fragmento de código de macro

 #if GCC_VERSION < 30300 # define __used __attribute__((__unused__)) #else # define __used __attribute__((__used__)) #endif 

Qual é o tipo de agilidade que os desenvolvedores de A fumam, a maneira dolorosamente tortuosa de seus pensamentos incorporados no código. Eu sei que você pode usar as duas formas de gravação de atributos, mas por que fazer isso na mesma linha - eu não entendo.

Uma característica mais interessante do código resultante pode ser observada - duplicação de informações sobre o nome da variável e seu tipo. Ainda não está claro por que isso foi feito, mas o fato em si não está em dúvida. Obviamente, essas informações são coerentes, pois são construídas no modo automático, e essa coerência será preservada quando o texto de origem for alterado (e isso é bom), mas duplicado (e isso é ruim), talvez mais tarde possamos entender a necessidade de uma solução desse tipo. Além disso, a necessidade de formar um nome exclusivo usando o número da linha do código-fonte permanece incerta, porque a primeira linha gerada ficou sem ela.

Outra observação - descobrir exatamente no que a definição do parâmetro se transforma não era uma tarefa completamente trivial, mas, graças ao MinGW, ele ainda estava concluído. Sob o capô, havia uma estringificação e colagem dupla de parâmetros, a formação de nomes únicos e outros truques complicados de trabalhar com macros, mas apresento apenas os resultados. Resumindo o resultado intermediário, posso dizer que o estudo das macros A não é o que eu gostaria de ganhar a vida, só é possível como entretenimento, mas continuamos.

Como não avançamos no entendimento das macros, entendemos a tarefa; portanto, recorremos ao código-fonte do utilitário And e tentamos entender o que ela faz.

Antes de tudo, estamos surpresos ao ver que os queijos necessários não estão incluídos nas fontes do kernel. Sim, estou pronto para concordar que o I é um utilitário e interage com o kernel através do ponto de entrada para carregar o módulo, mas qualquer livro sobre drivers L nos informa sobre esse utilitário, portanto, a falta de uma versão "oficial" de sua fonte em algum lugar próximo à fonte do kernel causa me entendendo mal. Bem, tudo bem, o Google não nos decepcionou, e todos nós continuamos com o queijo.

A segunda coisa surpreendente é que esse utilitário é formado a partir de um pacote cujo nome não está de forma alguma associado ao seu nome, há mais de um pacote e cada um é nomeado à sua maneira em diferentes locais - engraçado, para dizer o mínimo. Se você tiver L instalado, com o comando - poderá descobrir em qual pacote o utilitário And está montado e depois procurá-lo, mas se realizarmos pesquisas teóricas (eu pessoalmente não mantenho L no meu computador doméstico por várias razões, algumas das quais eu Afirmei meus posts, um boxeador teórico), então esse método não está disponível para nós e tudo o que resta é uma pesquisa na Internet, felizmente, ela dá resultados.

Bem, a terceira coisa surpreendente é que o nome do utilitário em si não aparece em nenhum lugar no código-fonte, não é usado nos nomes dos arquivos e é encontrado apenas no arquivo make, eu sei que em C somos obrigados a nomear a função principal como principal, e isso não é discutido (pessoalmente, não estou no Estou encantado com isso, já que Pascal é mimado, mas eles não pediram minha opinião ao criar o idioma), mas pelo menos seria possível escrever o nome externo do utilitário nos comentários. Nota necessária - muitas coisas em C foram feitas com o princípio "é tão habitual conosco", provavelmente era difícil tornar as coisas diferentes às vezes, ou até mesmo impossíveis, mas o que você pode fazer agora, arrastando uma mala sem uma alça adicional.

Nós encontramos dois pacotes contendo o texto fonte e também encontramos os queijos no github, vemos que eles são idênticos e acreditamos que é assim que o código fonte do utilitário se parece. Em seguida, estudamos apenas o arquivo no git, especialmente porque aqui é chamado apenas insmod.c, descobrimos que E para começar, ele converte a lista de parâmetros em uma cadeia longa terminada em nulo, na qual elementos individuais são separados por espaços. Depois disso, ele chama duas funções, a primeira delas chamada grub_file e, obviamente, abre o binário, enquanto a segunda tem o nome init_module e leva um ponteiro para o arquivo aberto com o módulo binário e uma cadeia de parâmetros e é chamada load_module, o que sugere o objetivo dessa função como carregar com modificação de parâmetros.

Passamos ao texto da segunda função, que está no arquivo ... e aqui está uma chatice - não em nenhum dos arquivos do repositório estudado no Geet (bem, isso é lógico, isso faz parte do kernel e seu lugar não está aqui) não é. Google novamente com pressa para ajudar e nos devolve aos queijos do kernel sob Elixir e o arquivo module.c. Note-se que, surpreendentemente, o nome do arquivo que contém as funções para trabalhar com módulos parece lógico, eu nem entendo como explicar isso, provavelmente aconteceu por acidente.

Agora ficou claro para nós a falta de texto. E ao lado do kernel - ele quase não faz nada, apenas transfere parâmetros de um formulário para outro e transfere o controle para o próprio núcleo, por isso é até indigno ficar ao lado dele. A partir deste momento, fica claro que não há informações externas claras sobre a estrutura dos parâmetros, uma vez que o kernel os pulou através de suas próprias macros e sabe tudo sobre eles perfeitamente, e o resto não precisa saber nada sobre a estrutura interna (à luz do fato de que a fonte estão disponíveis para visualização, alguns comentários não prejudicariam, mas, em princípio, são cada vez mais claros mesmo sem eles), mas até agora quase nunca lançaram luz sobre a implementação do próprio mecanismo de execução.

Nota - em relação à transferência de controle para o kernel, fiquei um pouco empolgado, vemos com certeza o uso da função no código fonte de um determinado código e se a parte binária será vinculada ao módulo ou se está realmente na imagem do kernel, é necessário investigar mais. O fato de o ponto de entrada para o processamento dessa função ser estruturado de uma maneira especial, via SYSCALL_DEFINE3, testemunha indiretamente a favor da segunda opção, mas há muito tempo entendo que minhas idéias sobre o lógico e o ilógico, o aceitável e o inaceitável, e também o permissível e o inaceitável desviar dos desenvolvedores de L.

Nota - mais uma pedrinha no jardim de pesquisa interno - ao procurar uma definição para essa macro, vi muitos lugares para usá-la como uma função, entre os quais a própria definição dela como macro se ocultava modestamente.

Por exemplo, não entendo por que um utilitário externo é necessário para converter os parâmetros do formulário padrão do sistema operacional (agrc, argv) na forma de uma sequência terminada em zero com espaços como delimitadores, que é processada posteriormente pelo módulo do sistema - essa abordagem ultrapassa de algum habilidades cognitivas. Especialmente, considerando que o usuário digita uma string de parâmetro na forma de uma string terminada em zero com espaços como separadores, e o utilitário no kernel a converte em uma forma (argc, argv). Remanescente da velha piada "Removemos a chaleira do fogão, despejamos água e obtemos um problema cuja solução já é conhecida". E como tento aderir ao princípio “considere seu interlocutor não mais estúpido que você, até que ele prove o contrário. E mesmo depois disso, você pode estar enganado ”, e com relação aos desenvolvedores de A, a primeira frase é definitivamente válida, significa que eu entendi mal alguma coisa, mas não estou acostumada. Se alguém puder oferecer uma explicação razoável do fato declarado da dupla conversão, pergunto no comentário. Mas continuamos a investigação.

As perspectivas para a implementação das opções 1 e 2 tornam-se "muito pouco visíveis" (uma redação encantadora de um artigo recente sobre as perspectivas de desenvolvimento de ADCs domésticos de alta velocidade), já que seria muito estranho carregar um módulo na memória usando a função kernel e passar o controle para implementar o kernel função embutida em seu corpo. E com certeza, no texto da função load_module, encontramos rapidamente a chamada parse_args - parece que estamos no caminho certo. Em seguida, passamos rapidamente pela cadeia de chamadas (como sempre, veremos as funções do wrapper e as macros do wrapper, mas já estamos acostumados a ignorar essas brincadeiras fofas dos desenvolvedores) e encontramos a função parse_one, que coloca o parâmetro necessário no lugar certo.

Observe que não há verificação da validade dos parâmetros, como seria de esperar, porque o kernel, ao contrário do próprio módulo, não sabe nada sobre seu objetivo. Existem verificações de sintaxe e o número de elementos na matriz (sim, pode haver uma matriz de números inteiros como parâmetro) e quando erros desse tipo são detectados, o carregamento do módulo para, mas nada mais. No entanto, nem tudo está perdido, porque após o controle de carregamento ser transferido para a função init_module, que pode realizar a validação necessária dos parâmetros definidos e, se o teste de salvamento for necessário, encerre o processo de inicialização.

No entanto, ignoramos completamente a questão de como as funções de análise acessam uma matriz de amostras de parâmetros, porque sem isso, a análise é um pouco difícil. Uma rápida olhada no código mostra que um hack sujo foi aplicado, um truque óbvio - no arquivo binário, a função find_module_sections procura a seção nomeada __param, divide seu tamanho pelo tamanho do registro (faz muito mais) e retorna os dados necessários através da estrutura. Eu ainda colocaria as letras p na frente dos nomes dos parâmetros dessa função, mas isso é uma questão de gosto.

Tudo parece claro e compreensível, a única coisa que preocupa é a ausência do atributo __initdata nos dados gerados, ele pode realmente permanecer na memória após a inicialização, provavelmente esse atributo é descrito em algum lugar da parte geral, por exemplo, nos dados do vinculador, para ser honesto , com preguiça de olhar, veja a epígrafe.

Resumindo: o fim de semana foi útil, foi interessante entender o código fonte de L, lembrar de algo e aprender algo, mas o conhecimento nunca é supérfluo.
Bem, em minhas suposições, eu não acho, em L foi implementada uma opção que se revelou nos 7% restantes, mas não foi dolorosamente óbvio.

Bem, em conclusão, o grito de Yaroslavna (como alguém poderia passar sem ele) por que preciso procurar as informações necessárias (não quero dizer a cozinha interna, mas a apresentação externa) de várias fontes que não têm status oficial, onde há um documento semelhante ao livro
“Software de um computador. Sistema operacional funcional.
RAFOS. System Programmer's Guide. ", Ou não é mais?

Source: https://habr.com/ru/post/pt431860/


All Articles