Em C, contexto é o estado atual de execução de um programa, incluindo registradores (pequenas áreas de armazenamento dentro da CPU, usadas para armazenar dados e instruções durante a execução de programas), variáveis e o fluxo de instruções, crucial para trocar tarefas.
Troca de contextos para sistemas operacionais
A principal função é permitir a multitarefa. Isso garante que o sistema possa alternar entre processos de forma eficiente.
Aqui foi disponibilizado o arquivo contexts.c
. É um demonstrativo de como contextos funcionam.
Logo no topo desse arquivo, percebemos a importação da biblioteca ucontext.h
. Ela permite manipular o contexto de execução.
No trecho abaixo vemos que são criados 3 contextos, e esses 3 contextos terão a memória alocada do tamanho de STACKSIZE
.
#define STACKSIZE 64 * 1024 /* tamanho de pilha das threads */
ucontext_t ContextPing, ContextPong, ContextMain;
E logo depois, as funções Ping e Pong que serão executadas em seus respectivos contextos:
void BodyPing(void *arg)
{
int i;
printf("%s: inicio\n", (char *)arg);
for (i = 0; i < 4; i++)
{
printf("%s: %d\n", (char *)arg, i);
swapcontext(&ContextPing, &ContextPong);
}
printf("%s: fim\n", (char *)arg);
swapcontext(&ContextPing, &ContextMain);
}
/*****************************************************/
void BodyPong(void *arg)
{
int i;
printf("%s: inicio\n", (char *)arg);
for (i = 0; i < 4; i++)
{
printf("%s: %d\n", (char *)arg, i);
swapcontext(&ContextPong, &ContextPing);
}
printf("%s: fim\n", (char *)arg);
swapcontext(&ContextPong, &ContextMain);
}
/*****************************************************/
Na função main, é utilizado malloc
para reservar as stacks, onde posteriormente são atribuidos com uc_stack.ss_sp
ao contexto, e swapcontext
é usado para alternar entre eles.
int main(int argc, char *argv[])
{
char *stack;
printf("main: inicio\n");
getcontext(&ContextPing);
stack = malloc(STACKSIZE);
if (stack)
{
ContextPing.uc_stack.ss_sp = stack;
ContextPing.uc_stack.ss_size = STACKSIZE;
ContextPing.uc_stack.ss_flags = 0;
ContextPing.uc_link = 0;
}
else
{
perror("Erro na criação da pilha: ");
exit(1);
}
makecontext(&ContextPing, (void *)(*BodyPing), 1, " Ping");
getcontext(&ContextPong);
stack = malloc(STACKSIZE);
if (stack)
{
ContextPong.uc_stack.ss_sp = stack;
ContextPong.uc_stack.ss_size = STACKSIZE;
ContextPong.uc_stack.ss_flags = 0;
ContextPong.uc_link = 0;
}
else
{
perror("Erro na criação da pilha: ");
exit(1);
}
makecontext(&ContextPong, (void *)(*BodyPong), 1, " Pong");
swapcontext(&ContextMain, &ContextPing);
swapcontext(&ContextMain, &ContextPong);
printf("main: fim\n");
exit(0);
}
Output do programa executado:
main: inicio
Ping: inicio
Ping: 0
Pong: inicio
Pong: 0
Ping: 1
Pong: 1
Ping: 2
Pong: 2
Ping: 3
Pong: 3
Ping: fim
Pong: fim
main: fim
Com isso, podemos perceber que mesmo alterando os contextos, os valores que "fluem" pela função são mantidos, um exemplo nesse caso é o índice do for
.
Você deve ter percebido que existe um malloc para o contexto de Ping e de Pong, mas vemos que existe um contexto para main
também, por qual motivo não existe um malloc para ele?
O ContextMain
não precisa de uma stack separada porque ele opera na stack do thread principal, enquanto os contextos Ping e Pong têm suas próprias stacks alocadas dinamicamente.
Se crio um contexto e não aloco memória para ele, quando utilizamos o swap
, ele vai para a stack principal do programa.
Esse código é do professor Maziero, encontrado no sub-projeto desenvolvido do PingPongOS "Trocas de Contexto".