Um sistema operacional injeta seu próprio código de máquina quando você abre um programa?

32

Estou estudando CPUs e sei como ele lê um programa da memória e executa suas instruções. Também entendo que um sistema operacional separa programas em processos e depois alterna entre eles tão rapidamente que você pensa que eles estão sendo executados ao mesmo tempo, mas, na verdade, cada programa é executado sozinho na CPU. Mas, se o sistema operacional também é um monte de código em execução na CPU, como ele pode gerenciar os processos?

Eu estive pensando e a única explicação que eu poderia pensar é: quando o sistema operacional carrega um programa da memória externa para a RAM, ele adiciona suas próprias instruções no meio das instruções originais do programa, para que o programa seja executado, o programa pode ligar para o sistema operacional e fazer algumas coisas. Eu acredito que há uma instrução que o sistema operacional irá adicionar ao programa, que permitirá que a CPU retorne ao código do sistema operacional algum tempo. Além disso, acredito que quando o sistema operacional carrega um programa, ele verifica se há algumas instruções proibidas (que iriam pular para endereços proibidos na memória) e as elimina.

Estou pensando direito? Eu não sou um estudante de CS, mas, de fato, um estudante de matemática. Se possível, eu gostaria de um bom livro sobre isso, porque não encontrei ninguém que explique como o sistema operacional pode gerenciar um processo se o sistema operacional também é um monte de código em execução na CPU e não pode ser executado ao mesmo tempo. hora do programa. Os livros dizem apenas que o sistema operacional pode gerenciar as coisas, mas agora como.

Reverendo Sumoda
fonte
7
Consulte: Troca de contexto O SO faz uma troca de contexto para o aplicativo. O aplicativo pode solicitar serviços do SO que fazem um contexto de volta ao SO. Quando o aplicativo termina, o contexto volta ao SO.
Guy Coder
4
Veja também "syscall".
Raphael
1
Se os comentários e as respostas não responderem à sua pergunta para sua compreensão ou satisfação, peça mais informações como comentário e explique o que você está pensando ou onde está perdido, ou sobre o que especificamente precisa de mais detalhes.
Guy Coder
2
Penso que interrupção , ligação (de uma interrupção), temporizador de hardware (com gancho de agendamento- manipulação) e paginação (resposta parcial à sua observação na memória proibida) são as principais palavras-chave de que você precisa. O sistema operacional precisa cooperar realmente com o processador para executar seu código somente quando necessário. Assim, a maior parte da energia da CPU pode ser usada no cálculo real, não no gerenciamento.
Palec

Respostas:

35

Não. O sistema operacional não mexe com o código do programa que injeta novo código nele. Isso teria várias desvantagens.

  1. Seria demorado, pois o sistema operacional teria que verificar todo o executável fazendo suas alterações. Normalmente, parte do executável é carregada apenas conforme necessário. Além disso, a inserção é cara, pois é necessário mover um monte de coisas para fora do caminho.

  2. Devido à indecidibilidade do problema de parada, é impossível saber onde inserir as instruções "Volte para o SO". Por exemplo, se o código incluir algo como while (true) {i++;}, você definitivamente precisará inserir um gancho dentro desse loop, mas a condição no loop ( true, aqui) pode ser arbitrariamente complicada, para que você não possa decidir por quanto tempo ele será executado. Por outro lado, seria muito ineficiente inserir ganchos em cada loop: por exemplo, saltar de volta para o sistema operacional durante for (i=0; i<3; i++) {j=j+i;}desaceleraria muito o processo. E, pelo mesmo motivo, você não pode detectar loops curtos para deixá-los em paz.

  3. Devido à indecidibilidade do problema de parada, é impossível saber se as injeções de código mudaram o significado do programa. Por exemplo, suponha que você use ponteiros de função no seu programa C. A injeção de novo código moveria os locais das funções. Assim, quando você chamava um pelo ponteiro, saltava para o lugar errado. Se o programador estivesse doente o suficiente para usar saltos computados, eles também falhariam.

  4. Seria um inferno para qualquer sistema antivírus, pois também mudaria o código do vírus e sujaria todas as suas somas de verificação.

Você pode solucionar o problema do problema de parada simulando o código e inserindo ganchos em qualquer loop que seja executado mais de um determinado número fixo de vezes. No entanto, isso exigiria uma simulação extremamente cara de todo o programa antes que ele fosse executado.

Na verdade, se você quisesse injetar código, o compilador seria o local natural para fazê-lo. Dessa forma, você precisaria fazê-lo apenas uma vez, mas ainda não funcionaria pela segunda e terceira razões mencionadas acima. (E alguém poderia escrever um compilador que não tocasse junto.)

Existem três maneiras principais pelas quais o sistema operacional recupera o controle dos processos.

  1. Nos sistemas cooperativos (ou não preventivos), há uma yieldfunção que um processo pode chamar para devolver o controle ao sistema operacional. Obviamente, se esse é o seu único mecanismo, você depende dos processos que estão se comportando bem e um processo que não produz irá sobrecarregar a CPU até que ela termine.

  2. Para evitar esse problema, uma interrupção do timer é usada. As CPUs permitem que o sistema operacional registre retornos de chamada para todos os diferentes tipos de interrupções implementadas pela CPU. O sistema operacional usa esse mecanismo para registrar um retorno de chamada para uma interrupção do timer que é acionada periodicamente, o que permite executar seu próprio código.

  3. Toda vez que um processo tenta ler um arquivo ou interagir com o hardware de qualquer outra maneira, é solicitado que o sistema operacional trabalhe para ele. Quando o sistema operacional é solicitado a fazer algo por um processo, ele pode decidir colocar esse processo em espera e começar a executar outro. Isso pode parecer um pouco maquiavélico, mas é a coisa certa a fazer: a E / S do disco é lenta, assim você também pode permitir que o processo B seja executado enquanto o processo A está aguardando os pedaços de metal girando para o lugar certo. A E / S de rede é ainda mais lenta. A E / S do teclado é glacial porque as pessoas não são seres gigahertz.

David Richerby
fonte
5
Você pode desenvolver mais seu último ponto? Estou curioso sobre esta questão e sinto que a explicação foi ignorada aqui. Parece-me que a pergunta é "como o sistema operacional retira a CPU do processo" e sua resposta diz "o sistema operacional lida com isso". mas como? Veja o loop infinito no seu primeiro exemplo: como ele não congela o computador?
BiAiB
3
Alguns sistemas operacionais fazer, mais OS, pelo menos bagunça com o código para fazer "ligando", para que o programa pode ser carregado em qualquer endereço
Ian Ringrose
1
@BiAiB A palavra-chave aqui é "interrupção". A CPU não é apenas algo que processa um determinado fluxo de instruções; também pode ser interrompida de forma assíncrona a partir de uma fonte separada - o mais importante para nós, interrupções de E / S e relógio. Como apenas o código de espaço no kernel pode lidar com interrupções, o Windows pode "roubar" o trabalho de qualquer processo em execução a qualquer momento. Os manipuladores de interrupção podem executar o código que quiserem, incluindo "armazenar os registros da CPU em algum lugar e restaurá-los a partir daqui (outro thread)". Extremamente simplificado, mas essa é a mudança de contexto.
Luaan
1
Adicionando a esta resposta; o estilo de multitarefa referido nos pontos 2 e 3 é chamado de "multitarefa preemptiva", o nome refere-se à capacidade do sistema operacional de antecipar um processo em execução. A multitarefa cooperativa era usada frequentemente em sistemas operacionais mais antigos; no Windows, pelo menos, a multitarefa preemptiva não foi introduzida até o Windows 95. Eu li sobre pelo menos um sistema de controle industrial em uso hoje que ainda usa o Windows 3.1 apenas para seu comportamento de multitarefa cooperativa em tempo real.
Jason C #
3
@BiAiB Na verdade, você está errado. As CPUs de desktop não executam código sequencialmente e de forma síncrona desde o i486. No entanto, mesmo as CPUs mais antigas ainda tinham entradas assíncronas - interrupções. Imagine uma solicitação de interrupção de hardware (IRQ) como um alfinete na própria CPU - quando recebe 1, a CPU para o que está fazendo e começa a processar a interrupção (que basicamente significa "preservar o estado e pular para um endereço na memória"). O tratamento de interrupções em si não é x86ou qualquer que seja o código, é literalmente conectado. Depois de pular, ele novamente executa (qualquer) x86código. Threads são uma abstração muito maior.
Luaan 7/07/2014
12

Embora a resposta de David Richerby seja boa, ela meio que espreita como os sistemas operacionais modernos interrompem os programas existentes. Minha resposta deve ser precisa para a arquitetura x86 ou x86_64, que é a única normalmente usada em desktops e laptops. Outras arquiteturas devem ter métodos semelhantes para conseguir isso.

Quando o sistema operacional está inicializando, ele configura uma tabela de interrupção. Cada entrada da tabela aponta para um pouco de código dentro do sistema operacional. Quando ocorrem interrupções, que são controladas pela CPU, ela olha para esta tabela e chama o código. Existem várias interrupções, como dividir por zero, código inválido e alguns definidos pelo sistema operacional.

É assim que o processo do usuário se comunica com o kernel, como se ele deseja ler / gravar no disco ou algo mais que o kernel do sistema operacional controla. Um sistema operacional também configurará um cronômetro que interromperá quando terminar, para que o código em execução seja alterado forçosamente do programa do usuário para o kernel do sistema operacional, e o kernel pode fazer outras coisas, como enfileirar outros programas para execução.

Na memória, quando isso acontece, o kernel do sistema operacional precisa salvar onde estava o código e, quando o kernel termina de fazer o que precisa, restaura o estado anterior do programa. Assim, o programa nem sabe que foi interrompido.

O processo não pode alterar a tabela de interrupção por dois motivos, o primeiro é que ele está sendo executado em um ambiente protegido; portanto, se tentar chamar determinado código de assembly protegido, o processador acionará outra interrupção. O segundo motivo é a memória virtual. O local da tabela de interrupções é de 0x0 a 0x3FF na memória real, mas com processos do usuário esse local geralmente não é mapeado, e tentar ler a memória não mapeada acionará outra interrupção, portanto, sem a função protegida e a capacidade de gravar na RAM real , o processo do usuário não pode alterá-lo.

Programmdude
fonte
4
As interrupções não são definidas pelo sistema operacional, são fornecidas pelo hardware. E a maioria das arquiteturas atuais possui instruções especiais para chamar o sistema operacional. O i386 usou uma interrupção (gerada por software) para isso, mas não é mais feito dessa maneira em seus sucessores.
vonbrand
2
Eu sei que as interrupções são definidas pela CPU, mas o kernel configura os ponteiros. Eu possivelmente expliquei mal. Eu também pensei que o linux usava o int 9 para conversar com o kernel ainda, mas talvez haja maneiras melhores agora.
Programmdude
Essa é uma resposta bastante enganadora, embora a noção de que planejadores preventivos sejam acionados por interrupções de timer esteja correta. Primeiro, vale a pena notar que o cronômetro está no hardware. Também para esclarecer que o processo "salvar ... restaurar" é chamado de opção de contexto e envolve principalmente salvar todos os registros da CPU (que inclui o ponteiro de instruções), entre outras coisas. Além disso, os processos podem alterar efetivamente as tabelas de interrupção, isso é chamado de "modo protegido", que também define a memória virtual, e existe desde o 286 - um ponteiro para a tabela de interrupção é armazenado em um registro gravável.
Jason C
(Além disso, mesmo o modo real de interrupção mesa foi relocatable - não bloqueado para a primeira página de memória - desde o 8086.)
Jason C
1
Esta resposta perde um detalhe crítico. Quando uma interrupção é acionada, a CPU não muda diretamente para o kernel. Em vez disso, ele primeiro salva os registros existentes, depois muda para outra pilha e somente então o kernel é chamado. Chamar o kernel com uma pilha aleatória de um programa aleatório seria uma péssima idéia. Além disso, a última parte é enganosa. Você não receberá uma interrupção "tentando" ler a memória não mapeada; é simplesmente impossível. Você lê de endereços virtuais e a memória não mapeada simplesmente não possui endereço virtual.
MSalters
5

O kernel do sistema operacional recupera o controle do processo em execução devido ao manipulador de interrupção do relógio da CPU, não injetando código no processo.

Você deve ler sobre interrupções para obter mais esclarecimentos sobre como eles funcionam e como os kernels do SO os lidam e implementam recursos diferentes.

Ankur
fonte
Não apenas o relógio interrompe: qualquer interrupção. E também instruções de alteração de modo.
Gilles 'SO- stop be evil'
3

Não é um método semelhante ao que você descreve: co-operative multitasking . O sistema operacional não insere instruções, mas cada programa deve ser escrito para chamar funções do sistema operacional que podem optar por executar outro processo cooperativo. Isso tem as desvantagens que você descreve: um travamento de programa remove todo o sistema. Windows até e inclusive 3.0 funcionava assim; 3.0 no "modo protegido" e acima não.

A multitarefa preventiva (o tipo normal atualmente) depende de uma fonte externa de interrupções. As interrupções substituem o fluxo normal de controle e geralmente salvam os registros em algum lugar, para que a CPU possa fazer outra coisa e, em seguida, retomar o programa de forma transparente. Obviamente, o sistema operacional pode alterar o registro "quando você deixar as interrupções retomar aqui", para retomar dentro de um processo diferente.

(Alguns sistemas fazer instruções de reescrita de forma limitada na carga programa, denominado "thunking", e o processador Transmeta recompilados dinamicamente para o seu próprio conjunto de instruções)

pjc50
fonte
O AFAICR 3.1 também foi cooperativo. O Win95 foi onde a multitarefa preemptiva entrou. O modo protegido trouxe principalmente o isolamento do espaço de endereço (o que melhora a estabilidade, mas por razões não relacionadas).
cHao 07/07
O thunking não reescreve ou injeta código no aplicativo. O carregador que é modificado é baseado no SO e não é um produto do aplicativo. Linguagens interpretativas compiladas, como o uso de compiladores JIT, não modificam o código nem injetam nada no código. Eles traduzem o código fonte em um executável. Novamente, isso não é o mesmo que injetar código em um aplicativo.
Dave Gordon
A Transmeta tomou o código executável x86 como fonte, não como linguagem interpretativa. E pensei em um caso em que o código é injetado: rodando em um depurador. Os sistemas X86 geralmente substituem a instrução no ponto de interrupção com "INT 03", que intercepta no depurador. Ao retomar, o opcode original é restabelecido.
Pjc50
Depurar dificilmente é como alguém executa um aplicativo; além do do desenvolvedor do aplicativo. Então, eu não acho que isso realmente ajude o OP.
Dave Gordon
3

A multitarefa não requer nada como injeção de código. Em um sistema operacional como o Windows, há um componente do código do sistema operacional chamado agendador, que depende de uma interrupção de hardware acionada por um timer de hardware. Isso é usado pelo sistema operacional para alternar entre diferentes programas e ele próprio, fazendo com que tudo pareça à nossa percepção humana acontecer simultaneamente.

Basicamente, o sistema operacional programa o temporizador de hardware para disparar de vez em quando ... talvez 100 vezes por segundo. Quando o timer dispara, ele gera uma interrupção de hardware - um sinal que diz à CPU para parar o que está fazendo, salvar seu estado na pilha, mudar seu modo para algo mais privilegiado e executar o código que encontrará em um local especialmente designado. coloque na memória. Esse código faz parte do planejador, que decide o que deve ser feito a seguir. Pode ser retomar outro processo, nesse caso, ele terá que executar o que é conhecido como "alternância de contexto" - substituindo todo o seu estado atual (incluindo tabelas de memória virtual) pelo do outro processo. Ao retornar a um processo, ele precisa restaurar todo o contexto desse processo,

O local "especialmente designado" na memória não precisa ser conhecido por nada além do sistema operacional. As implementações variam, mas a essência é que a CPU responderá a várias interrupções executando uma pesquisa de tabela; a localização da tabela está em um local específico na memória (determinado pelo design do hardware da CPU), o conteúdo da tabela é definido pelo sistema operacional (geralmente no momento da inicialização) e o "tipo" de interrupção determinará qual entrada na tabela deve ser usada como a "rotina de serviço de interrupção".

Nada disso envolve "injeção de código" ... é baseado no código contido no sistema operacional em cooperação com os recursos de hardware da CPU e seus circuitos de suporte.

Zenilogix
fonte
2

Acho que o exemplo do mundo real mais próximo do que você descreve é uma das técnicas usadas pelo VMware , virtualização completa usando tradução binária .

O VMware atua como uma camada abaixo de um ou mais sistemas operacionais em execução simultânea no mesmo hardware.

A maioria das instruções executadas (por exemplo, em aplicativos comuns) pode ser virtualizada usando o hardware, mas o próprio kernel do sistema operacional utiliza instruções que não podem ser virtualizadas, porque se o código da máquina do sistema operacional de adivinhação fosse executado sem modificação, "seria interrompido" "do controle do host VMware. Por exemplo, um sistema operacional convidado precisaria executar no anel de proteção mais privilegiado e configurar a tabela de interrupção. Se fosse permitido, a VMware teria perdido o controle do hardware.

O VMware reescreve essas instruções no código do sistema operacional antes de executá-las, substituindo-as por saltos no código do VMware que simula o efeito desejado.

Portanto, essa técnica é um pouco análoga ao que você descreve.

Daniel Earwicker
fonte
2

Há vários casos em que um sistema operacional pode "injetar código" em um programa. As versões baseadas no 68000 do sistema Apple Macintosh constroem uma tabela de todos os pontos de entrada do segmento (localizados imediatamente antes das variáveis ​​globais estáticas, IIRC). Quando um programa é iniciado, cada entrada na tabela consiste em uma instrução de interceptação seguida pelo número do segmento e deslocada para o segmento. Se a interceptação for executada, o sistema examinará as palavras após a instrução de interceptação para ver qual segmento e deslocamento são necessários, carregar o segmento (se ainda não o for), adicionar o endereço inicial do segmento ao deslocamento e substitua a armadilha por um salto para esse endereço recém-calculado.

No software de PC mais antigo, embora isso não tenha sido tecnicamente feito pelo "SO", era comum o código ser criado com instruções de interceptação, em vez de instruções matemáticas do coprocessador. Se nenhum coprocessador matemático estivesse instalado, o manipulador de interceptação o emularia. Se um coprocessador foi instalado, na primeira vez que uma captura é realizada, o manipulador substitui a instrução de captura por uma instrução de coprocessador; futuras execuções do mesmo código usarão diretamente a instrução do coprocessador.

supercat
fonte
O método FP ainda está em uso nos processadores ARM, que, diferentemente das CPUs x86, ainda possuem variantes sem FP. Mas é raro, pois a maior parte do uso do ARM é em dispositivos dedicados. Nesses ambientes, normalmente é conhecido se a CPU terá recursos de FP.
MSalters
Em nenhum desses casos, o sistema operacional injetou código no aplicativo. Para o sistema operacional injetar código, seria necessária uma licença do fornecedor do software para "modificar" o aplicativo que ele não obtém. OS não injetam código.
Dave Gordon
As instruções do @DaveGordon Trapped podem ser consideradas razoavelmente o código de injeção do SO no aplicativo.
Gilles 'SO- stop be evil'
As instruções interceptadas do @MSalters ocorrem normalmente em máquinas virtuais.
Gilles 'SO- stop be evil'