tipos sistemas sistema recursos procesos proceso operativos operativo gestor gestion estados contexto cambio c linux context-switch

sistemas - gestor de recursos sistema operativo



Escriba un programa en C para medir el tiempo empleado en el cambio de contexto en el sistema operativo Linux (6)

¿Por qué no solo esta como una estimación aproximada?

#include <ctime> #include <cstdio> #include <sys/time.h> #include <unistd.h> int main(int argc, char **argv) { struct timeval tv, tvt; int diff; gettimeofday(&tv, 0); diff = tvt.tv_usec - tv.tv_usec; if (fork() != 0) { gettimeofday(&tvt, 0); diff = tvt.tv_usec - tv.tv_usec; printf("%d/n", diff); } return 0; }

Nota: en realidad no deberíamos poner nulo como segundo argumento, verifique man gettimeofday. Además, deberíamos verificar si tvt.tv_usec> tv.tv_usec! Sólo un borrador.

¿Podemos escribir un programa de CA para averiguar el tiempo empleado en el cambio de contexto en Linux? ¿Podrías compartir el código si tienes uno? Gracias


Es muy difícil perfilar el tiempo de conmutación, pero las herramientas de perfilado de latencia en el kernel, así como el oprofile (que puede perfilar el kernel en sí) lo ayudarán allí.

Para evaluar el rendimiento de la aplicación interactiva, he escrito una pequeña herramienta llamada latencybench que mide los picos de latencia inesperados:

// Compile with g++ latencybench.cc -o latencybench -lboost_thread-mt // Should also work on MSVC and other platforms supported by Boost. #include <boost/format.hpp> #include <boost/thread/thread.hpp> #include <boost/date_time.hpp> #include <algorithm> #include <cstdlib> #include <csignal> volatile bool m_quit = false; extern "C" void sighandler(int) { m_quit = true; } std::string num(unsigned val) { if (val == 1) return "one occurrence"; return boost::lexical_cast<std::string>(val) + " occurrences"; } int main(int argc, char** argv) { using namespace boost::posix_time; std::signal(SIGINT, sighandler); std::signal(SIGTERM, sighandler); time_duration duration = milliseconds(10); if (argc > 1) { try { if (argc != 2) throw 1; unsigned ms = boost::lexical_cast<unsigned>(argv[1]); if (ms > 1000) throw 2; duration = milliseconds(ms); } catch (...) { std::cerr << "Usage: " << argv[0] << " milliseconds" << std::endl; return EXIT_FAILURE; } } typedef std::map<long, unsigned> Durations; Durations durations; unsigned samples = 0, wrongsamples = 0; unsigned max = 0; long last = -1; std::cout << "Measuring actual sleep delays when requesting " << duration.total_milliseconds() << " ms: (Ctrl+C when done)" << std::endl; ptime begin = boost::get_system_time(); while (!m_quit) { ptime start = boost::get_system_time(); boost::this_thread::sleep(start + duration); long actual = (boost::get_system_time() - start).total_milliseconds(); ++samples; unsigned num = ++durations[actual]; if (actual != last) { std::cout << "/r " << actual << " ms " << std::flush; last = actual; } if (actual != duration.total_milliseconds()) { ++wrongsamples; if (num > max) max = num; std::cout << "spike at " << start - begin << std::endl; last = -1; } } if (samples == 0) return 0; std::cout << "/rTotal measurement duration: " << boost::get_system_time() - begin << "/n"; std::cout << "Number of samples collected: " << samples << "/n"; std::cout << "Incorrect delay count: " << wrongsamples << boost::format(" (%.2f %%)") % (100.0 * wrongsamples / samples) << "/n/n"; std::cout << "Histogram of actual delays:/n/n"; unsigned correctsamples = samples - wrongsamples; const unsigned line = 60; double scale = 1.0; char ch = ''+''; if (max > line) { scale = double(line) / max; ch = ''*''; } double correctscale = 1.0; if (correctsamples > line) correctscale = double(line) / correctsamples; for (Durations::const_iterator it = durations.begin(); it != durations.end(); ++it) { std::string bar; if (it->first == duration.total_milliseconds()) bar = std::string(correctscale * it->second, ''>''); else bar = std::string(scale * it->second, ch); std::cout << boost::format("%5d ms | %s %d") % it->first % bar % it->second << std::endl; } std::cout << "/n"; std::string indent(30, '' ''); std::cout << indent << "+-- Legend ----------------------------------/n"; std::cout << indent << "| > " << num(1.0 / correctscale) << " (of " << duration.total_milliseconds() << " ms delay)/n"; if (wrongsamples > 0) std::cout << indent << "| " << ch << " " << num(1.0 / scale) << " (of any other delay)/n"; }

Resultados en Ubuntu 2.6.32-14-kernel genérico. Mientras medía, estaba compilando código C ++ con cuatro núcleos y jugando un juego con gráficos OpenGL al mismo tiempo (para hacerlo más interesante):

Total measurement duration: 00:01:45.191465 Number of samples collected: 10383 Incorrect delay count: 196 (1.89 %) Histogram of actual delays: 10 ms | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 10187 11 ms | *************************************************** 70 12 ms | ************************************************************ 82 13 ms | ********* 13 14 ms | ********* 13 15 ms | ** 4 17 ms | *** 5 18 ms | * 2 19 ms | **** 6 20 ms | 1 +-- Legend ---------------------------------- | > 169 occurrences (of 10 ms delay) | * one occurrence (of any other delay)

Con los kernels rt-parcheados obtengo resultados mucho mejores, casi 10 a 12 ms solamente.

La leyenda en la impresión parece estar sufriendo un error de redondeo o algo (y el código fuente pegado no es exactamente la misma versión). Realmente nunca pulí esta aplicación para un lanzamiento ...


Medir el costo de un cambio de contexto es un poco más complicado. Podemos calcular el tiempo empleado en el cambio de contexto ejecutando dos procesos en una sola CPU y configurando tres conductos de Linux entre ellos;

  • Dos tuberías para compartir la cadena entre el proceso y
  • El tercero se usará para compartir el tiempo empleado en el proceso secundario.

El primer proceso luego emite una escritura en el primer canal y espera una lectura en el segundo; al ver el primer proceso esperando que se lea algo de la segunda canalización, el sistema operativo pone el primer proceso en el estado bloqueado y cambia al otro proceso, que lee desde la primera tubería y luego escribe en la segunda. Cuando el segundo proceso intenta volver a leer desde la primera tubería, se bloquea, y así continúa el ciclo de comunicación de ida y vuelta. Al medir el costo de la comunicación repetidamente de esta manera, puede hacer una buena estimación del costo de un cambio de contexto.

Una dificultad para medir el costo del cambio de contexto surge en sistemas con más de una CPU; Lo que debe hacer en dicho sistema es asegurarse de que los procesos de cambio de contexto se encuentren en el mismo procesador. Afortunadamente, la mayoría de los sistemas operativos tienen llamadas para vincular un proceso a un procesador particular; en Linux, por ejemplo, la llamada sched_setaffinity () es lo que está buscando. Al asegurarse de que ambos procesos estén en el mismo procesador, se asegura de medir el costo del sistema operativo, deteniendo un proceso y restaurando otro en la misma CPU.

Aquí estoy publicando mi solución para computar el cambio de contexto entre procesos.

#define _GNU_SOURCE #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <sched.h> #include <stdlib.h> #include <string.h> #include <linux/unistd.h> #include <sys/time.h> #include <unistd.h> #include <sys/syscall.h> #include <errno.h> pid_t getpid( void ) { return syscall( __NR_getpid ); } int main() { /********************************************************************************************* To make sure context-switching processes are located on the same processor : 1. Bind a process to a particular processor using sched_setaffinity. 2. To get the maximum priority value (sched_get_priority_max) that can be used with the scheduling algorithm identified by policy (SCHED_FIFO).** **********************************************************************************************/ cpu_set_t set; struct sched_param prio_param; int prio_max; CPU_ZERO( &set ); CPU_SET( 0, &set ); memset(&prio_param,0,sizeof(struct sched_param)); if (sched_setaffinity( getpid(), sizeof( cpu_set_t ), &set )) { perror( "sched_setaffinity" ); exit(EXIT_FAILURE); } if( (prio_max = sched_get_priority_max(SCHED_FIFO)) < 0 ) { perror("sched_get_priority_max"); } prio_param.sched_priority = prio_max; if( sched_setscheduler(getpid(),SCHED_FIFO,&prio_param) < 0 ) { perror("sched_setscheduler"); exit(EXIT_FAILURE); } /***************************************************************************************************** 1. To create a pipe for a fork, the parent and child processes use pipe to read and write, read and write string, using this for context switch. 2. The parent process first to get the current timestamp (gettimeofday), then write to the pipe,. Then the child should be read in from the back, then the child process to write string, the parent process reads. After the child process to get the current timestamp. This is roughly the difference between two timestamps n * 2 times the context switch time. *******************************************************************************************************/ int ret=-1; int firstpipe[2]; int secondpipe[2]; int timepipe[2]; int nbytes; char string[] = "Hello, world!/n"; char temp[] = "Sumit Gemini!/n"; char readbuffer[80]; char tempbuffer[80]; int i=0; struct timeval start,end; // Create an unnamed first pipe if (pipe(firstpipe) == -1) { fprintf(stderr, "parent: Failed to create pipe/n"); return -1; } // create an unnamed Second pipe if (pipe(secondpipe) == -1) { fprintf(stderr, "parent: Failed to create second pipe/n"); return -1; } // Create an unnamed time pipe which will share in order to show time spend between processes if (pipe(timepipe) == -1) { fprintf(stderr, "parent: Failed to create time pipe/n"); return -1; } if((ret=fork())==-1) perror("fork"); else if(ret==0) { int n=-1; printf("Child ----> %d/n",getpid()); for(n=0;n<5;n++) { nbytes = read(firstpipe[0], readbuffer, sizeof(readbuffer)); printf("Received string: %s", readbuffer); write(secondpipe[1], temp, strlen(temp)+1); } gettimeofday(&end,0); n = sizeof(struct timeval); if( write(timepipe[1],&end,sizeof(struct timeval)) != n ) { fprintf(stderr, "child: Failed to write in time pipe/n"); exit(EXIT_FAILURE); } } else { double switch_time; int n=-1; printf("Parent ----> %d/n",getpid()); gettimeofday(&start,0); /* Read in a string from the pipe */ for(n=0;n<5;n++) { write(firstpipe[1], string, strlen(string)+1); read(secondpipe[0], tempbuffer, sizeof(tempbuffer)); printf("Received temp: %s", tempbuffer); } n = sizeof(struct timeval); if( read(timepipe[0],&end,sizeof(struct timeval)) != n ) { fprintf(stderr, "Parent: Failed to read from time pipe/n"); exit(EXIT_FAILURE); } wait(NULL); switch_time = ((end.tv_sec-start.tv_sec)*1000000+(end.tv_usec-start.tv_usec))/1000.0; printf("context switch between two processes: %0.6lfms/n",switch_time/(5*2)); } return 0; }


Qué piensas, midiendo el cambio de contexto en segundos o milisegundos o incluso microsegundos. Todo sucediendo menos que nano-sec. Si desea dedicar tanto tiempo al cambio de contexto que podría medirse, entonces ... Pruebe un código de tipo de kernel en modo real escrito en Ensamblaje, es posible que vea algo.


Respuesta corta - no. Larga respuesta abajo.

El cambio de contexto ocurre aproximadamente cuando:

  1. El proceso del usuario ingresa al kernel a través de una llamada al sistema o una captura (por ejemplo, fallo de página) y los datos solicitados (por ejemplo, el contenido del archivo) aún no están disponibles, por lo que el kernel pone dicho proceso de usuario en estado de suspensión y cambia a otro proceso ejecutable.
  2. El kernel detecta que el proceso del usuario dado consume su cantidad de tiempo completo (esto sucede en el código invocado desde la interrupción del temporizador).
  3. Los datos se vuelven disponibles para el proceso de prioridad actual más alta que actualmente está inactivo (esto sucede desde el código invocado desde / alrededor de las interrupciones de IO).

El cambio en sí mismo es unidireccional, por lo que lo mejor que podemos hacer en la zona de usuario (supongo que eso es lo que está preguntando) es medir una especie de RTT, de nuestro proceso a otro y viceversa. El otro proceso también toma tiempo para hacer su trabajo. Por supuesto, podemos hacer que dos o más procesos cooperen en esto, pero el problema es que el núcleo no garantiza que uno de nuestros procesos será seleccionado a continuación. Probablemente es posible cambiar a un proceso dado con el programador RT, pero no tengo ningún consejo aquí, sugerencias bienvenidas.


Si tiene privilegios de superusuario, puede ejecutar un programa SystemTap con puntos de prueba para cambios de contexto e imprimir la hora actual en cada uno:

probe scheduler.ctxswitch { printf("Switch from %d to %d at %d/n", prev_pid, next_pid, gettimeofday_us()) }

No estoy seguro de cuán confiables son los datos de salida, pero es una forma rápida y fácil de obtener algunos números.