sergio/takana
> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
⎇ main
docs/evidencia/tasas-kernel-2026-08-29/bench_connector.c
Ñawpa willay
Pi imata llamkarqan
ch'uya
tasas: los 4 experimentos que §9 dejó nombrados, medidos con root
1/* bench_connector.c — proc connector (netlink) y taskstats contra sondear /proc.
2 *
3 * Cierra el experimento 1 de SDD 25 §9, que quedo sin medir por falta de CAP_NET_ADMIN.
4 * Requiere root. Tres fases:
5 * A. LATENCIA fork+_exit+waitpid vs fork+_exit+esperar el evento del connector.
6 * B. CEGUERA procesos cortos vistos por el connector vs vistos sondeando /proc cada 100 ms.
7 * C. TASKSTATS una consulta binaria por genetlink vs parsear /proc/<pid>/stat.
8 *
9 * Se compila con: cc -O2 -o bench_connector bench_connector.c
10 */
11#define _GNU_SOURCE
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <unistd.h>
16#include <fcntl.h>
17#include <time.h>
18#include <errno.h>
19#include <dirent.h>
20#include <sys/socket.h>
21#include <sys/wait.h>
22#include <sys/resource.h>
23#include <poll.h>
24#include <linux/netlink.h>
25#include <linux/connector.h>
26#include <linux/cn_proc.h>
27#include <linux/genetlink.h>
28#include <linux/taskstats.h>
29
30static volatile long sink;
31static inline double now_ns(void){ struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return t.tv_sec*1e9+t.tv_nsec; }
32static int cmpd(const void*a,const void*b){ double x=*(const double*)a,y=*(const double*)b; return (x>y)-(x<y); }
33static void report(const char*n,double*r,int c,long it){ qsort(r,c,sizeof(double),cmpd);
34 printf("%-44s %10.1f %10.1f (n=%ld)\n",n,r[0],r[c/2],it); fflush(stdout); }
35#define ROUNDS 7
36
37/* ---------------------------------------------------------------- proc connector */
38
39static int cn_open(void){
40 int s = socket(PF_NETLINK, SOCK_DGRAM|SOCK_CLOEXEC, NETLINK_CONNECTOR);
41 if(s<0) return -1;
42 struct sockaddr_nl a; memset(&a,0,sizeof a);
43 a.nl_family=AF_NETLINK; a.nl_groups=CN_IDX_PROC; a.nl_pid=getpid();
44 if(bind(s,(struct sockaddr*)&a,sizeof a)<0){ close(s); return -1; }
45
46 struct {
47 struct nlmsghdr nl;
48 struct cn_msg cn;
49 enum proc_cn_mcast_op op;
50 } __attribute__((packed)) m;
51 memset(&m,0,sizeof m);
52 m.nl.nlmsg_len=sizeof m; m.nl.nlmsg_pid=getpid(); m.nl.nlmsg_type=NLMSG_DONE;
53 m.cn.id.idx=CN_IDX_PROC; m.cn.id.val=CN_VAL_PROC; m.cn.len=sizeof(enum proc_cn_mcast_op);
54 m.op=PROC_CN_MCAST_LISTEN;
55 if(send(s,&m,sizeof m,0)<0){ close(s); return -1; }
56 /* El multicast del connector NO reintenta: si el buffer se llena, el evento se PIERDE (ENOBUFS)
57 * y el que espera ese pid se queda esperando para siempre. Se agranda el buffer y, aun asi, la
58 * espera lleva plazo (abajo). Es una propiedad del mecanismo, no un detalle de este programa. */
59 int rb=4*1024*1024; setsockopt(s,SOL_SOCKET,SO_RCVBUFFORCE,&rb,sizeof rb);
60 return s;
61}
62
63/* Lee un evento. Devuelve el proc_event, o NULL si el mensaje no lo era. */
64static struct proc_event *cn_read(int s, char *buf, size_t n){
65 ssize_t r = recv(s, buf, n, 0);
66 if(r<=0) return NULL;
67 struct nlmsghdr *nlh=(struct nlmsghdr*)buf;
68 if(!NLMSG_OK(nlh,(size_t)r)) return NULL;
69 struct cn_msg *cn=(struct cn_msg*)NLMSG_DATA(nlh);
70 return (struct proc_event*)cn->data;
71}
72
73/* Vacia lo que haya en el socket sin bloquear. */
74static void cn_drain(int s){
75 int fl=fcntl(s,F_GETFL,0); fcntl(s,F_SETFL,fl|O_NONBLOCK);
76 char b[4096]; while(recv(s,b,sizeof b,0)>0){}
77 fcntl(s,F_SETFL,fl);
78}
79
80static void fase_a(int s){
81 printf("\n### A. Latencia de notificacion: waitpid contra evento del connector\n");
82 printf("%-44s %10s %10s\n","BENCH (ns por proceso)","min","mediana");
83 const long N=200;
84
85 double r[ROUNDS];
86 for(int q=0;q<ROUNDS;q++){
87 double t0=now_ns();
88 for(long i=0;i<N;i++){ pid_t p=fork(); if(p==0) _exit(0); int st; waitpid(p,&st,0); }
89 r[q]=(now_ns()-t0)/(double)N;
90 }
91 report("fork+_exit+waitpid (SIGCHLD, la base)",r,ROUNDS,N);
92
93 char buf[8192];
94 long perdidos=0;
95 for(int q=0;q<ROUNDS;q++){
96 cn_drain(s);
97 double t0=now_ns();
98 for(long i=0;i<N;i++){
99 pid_t p=fork(); if(p==0) _exit(0);
100 double lim=now_ns()+2e8; /* 200 ms de plazo: si no llega, se PERDIO */
101 for(;;){
102 if(now_ns()>lim){ perdidos++; break; }
103 struct pollfd pf={.fd=s,.events=POLLIN};
104 if(poll(&pf,1,50)<=0) continue;
105 struct proc_event *e=cn_read(s,buf,sizeof buf);
106 if(!e) continue;
107 if(e->what==PROC_EVENT_EXIT && e->event_data.exit.process_pid==p) break;
108 }
109 int st; waitpid(p,&st,0);
110 }
111 r[q]=(now_ns()-t0)/(double)N;
112 }
113 report("fork+_exit+esperar EXIT del connector",r,ROUNDS,N);
114 printf("eventos EXIT que no llegaron en 200 ms: %ld de %ld\n",perdidos,(long)N*ROUNDS);
115}
116
117/* ---------------------------------------------------------------- ceguera del sondeo */
118
119#define BURST 200 /* procesos cortos */
120#define SPACING_US 10000 /* uno cada 10 ms => 2 s de rafaga */
121#define POLL_MS 100 /* periodo del sondeo, generoso: un supervisor real no baja de aca */
122
123/* Lanza BURST hijos que viven lo minimo, y escribe sus pids por el pipe. */
124static void spawner(int wfd){
125 for(int i=0;i<BURST;i++){
126 pid_t p=fork();
127 if(p==0) _exit(0);
128 (void)!write(wfd,&p,sizeof p);
129 int st; waitpid(p,&st,0);
130 usleep(SPACING_US);
131 }
132 close(wfd);
133 _exit(0);
134}
135
136static int en_lista(pid_t *v,int n,pid_t p){ for(int i=0;i<n;i++) if(v[i]==p) return 1; return 0; }
137
138static double cpu_ms(void){
139 struct rusage u; getrusage(RUSAGE_SELF,&u);
140 return (u.ru_utime.tv_sec+u.ru_stime.tv_sec)*1e3
141 + (u.ru_utime.tv_usec+u.ru_stime.tv_usec)/1e3;
142}
143
144static void fase_b(int s){
145 printf("\n### B. Ceguera: %d procesos de vida minima, uno cada %d ms\n",BURST,SPACING_US/1000);
146
147 /* --- observador 1: sondeo de /proc cada POLL_MS --- */
148 int fd[2]; if(pipe(fd)) { perror("pipe"); return; }
149 pid_t sp=fork();
150 if(sp==0){ close(fd[0]); spawner(fd[1]); }
151 close(fd[1]);
152 static pid_t lanzados[BURST]; int nl=0;
153 static pid_t vistos[BURST*8]; int nv=0;
154 double c0=cpu_ms(), t0=now_ns(); int barridos=0;
155 while(now_ns()-t0 < (double)BURST*SPACING_US*1000.0 + 3e8){
156 DIR *d=opendir("/proc"); struct dirent *e;
157 while(d && (e=readdir(d))){
158 if(e->d_name[0]<'0'||e->d_name[0]>'9') continue;
159 pid_t p=(pid_t)atol(e->d_name);
160 if(nv<BURST*8 && !en_lista(vistos,nv,p)) vistos[nv++]=p;
161 }
162 if(d) closedir(d);
163 barridos++;
164 usleep(POLL_MS*1000);
165 }
166 double cpu_sondeo=cpu_ms()-c0;
167 { int st; waitpid(sp,&st,0); }
168 { pid_t p; while(read(fd[0],&p,sizeof p)==sizeof p) if(nl<BURST) lanzados[nl++]=p; }
169 close(fd[0]);
170 int cazados=0; for(int i=0;i<nl;i++) if(en_lista(vistos,nv,lanzados[i])) cazados++;
171 printf("sondeo /proc cada %d ms : %d de %d procesos vistos (%d barridos, %.1f ms de CPU)\n",
172 POLL_MS,cazados,nl,barridos,cpu_sondeo);
173
174 /* --- observador 2: connector --- */
175 if(pipe(fd)){ perror("pipe"); return; }
176 cn_drain(s);
177 sp=fork();
178 if(sp==0){ close(fd[0]); spawner(fd[1]); }
179 close(fd[1]);
180 nl=0; nv=0;
181 c0=cpu_ms(); t0=now_ns();
182 int flags=fcntl(s,F_GETFL,0); fcntl(s,F_SETFL,flags|O_NONBLOCK);
183 char buf[8192];
184 while(now_ns()-t0 < (double)BURST*SPACING_US*1000.0 + 3e8){
185 struct proc_event *e=cn_read(s,buf,sizeof buf);
186 if(!e){ usleep(200); continue; }
187 if(e->what==PROC_EVENT_EXIT){
188 pid_t p=e->event_data.exit.process_pid;
189 if(nv<BURST*8 && !en_lista(vistos,nv,p)) vistos[nv++]=p;
190 }
191 }
192 fcntl(s,F_SETFL,flags);
193 double cpu_conn=cpu_ms()-c0;
194 { int st; waitpid(sp,&st,0); }
195 { pid_t p; while(read(fd[0],&p,sizeof p)==sizeof p) if(nl<BURST) lanzados[nl++]=p; }
196 close(fd[0]);
197 cazados=0; for(int i=0;i<nl;i++) if(en_lista(vistos,nv,lanzados[i])) cazados++;
198 printf("proc connector (eventos) : %d de %d procesos vistos (%.1f ms de CPU)\n",
199 cazados,nl,cpu_conn);
200 printf("NOTA: el connector se lee aca con espera activa de 200 us, que NO es el modo de un\n"
201 " supervisor real (seria epoll) => su CPU es cota superior. Lo que cuenta: COBERTURA.\n");
202}
203
204/* ---------------------------------------------------------------- taskstats */
205
206static int genl_family(int s,const char *name){
207 struct { struct nlmsghdr n; struct genlmsghdr g; char buf[256]; } req;
208 memset(&req,0,sizeof req);
209 req.n.nlmsg_type=GENL_ID_CTRL; req.n.nlmsg_flags=NLM_F_REQUEST; req.n.nlmsg_seq=1; req.n.nlmsg_pid=getpid();
210 req.g.cmd=CTRL_CMD_GETFAMILY; req.g.version=1;
211 struct nlattr *a=(struct nlattr*)req.buf;
212 a->nla_type=CTRL_ATTR_FAMILY_NAME; a->nla_len=NLA_HDRLEN+strlen(name)+1;
213 strcpy((char*)a+NLA_HDRLEN,name);
214 req.n.nlmsg_len=NLMSG_LENGTH(GENL_HDRLEN)+NLA_ALIGN(a->nla_len);
215 if(send(s,&req,req.n.nlmsg_len,0)<0) return -1;
216
217 char rb[4096]; ssize_t r=recv(s,rb,sizeof rb,0);
218 if(r<0) return -1;
219 struct nlmsghdr *nh=(struct nlmsghdr*)rb;
220 if(nh->nlmsg_type==NLMSG_ERROR) return -1;
221 struct nlattr *na=(struct nlattr*)((char*)NLMSG_DATA(nh)+GENL_HDRLEN);
222 int len=NLMSG_PAYLOAD(nh,0)-GENL_HDRLEN;
223 while(len>0){
224 if(na->nla_type==CTRL_ATTR_FAMILY_ID) return *(__u16*)((char*)na+NLA_HDRLEN);
225 len-=NLA_ALIGN(na->nla_len);
226 na=(struct nlattr*)((char*)na+NLA_ALIGN(na->nla_len));
227 }
228 return -1;
229}
230
231static int ts_query(int s,int fam,pid_t pid,unsigned long long *cpu_ns){
232 struct { struct nlmsghdr n; struct genlmsghdr g; char buf[128]; } req;
233 memset(&req,0,sizeof req);
234 req.n.nlmsg_type=fam; req.n.nlmsg_flags=NLM_F_REQUEST; req.n.nlmsg_seq=2; req.n.nlmsg_pid=getpid();
235 req.g.cmd=TASKSTATS_CMD_GET; req.g.version=TASKSTATS_VERSION;
236 struct nlattr *a=(struct nlattr*)req.buf;
237 a->nla_type=TASKSTATS_CMD_ATTR_PID; a->nla_len=NLA_HDRLEN+sizeof(__u32);
238 *(__u32*)((char*)a+NLA_HDRLEN)=pid;
239 req.n.nlmsg_len=NLMSG_LENGTH(GENL_HDRLEN)+NLA_ALIGN(a->nla_len);
240 if(send(s,&req,req.n.nlmsg_len,0)<0) return -1;
241
242 char rb[8192]; ssize_t r=recv(s,rb,sizeof rb,0);
243 if(r<0) return -1;
244 struct nlmsghdr *nh=(struct nlmsghdr*)rb;
245 if(nh->nlmsg_type==NLMSG_ERROR) return -1;
246 struct nlattr *na=(struct nlattr*)((char*)NLMSG_DATA(nh)+GENL_HDRLEN);
247 int len=NLMSG_PAYLOAD(nh,0)-GENL_HDRLEN;
248 while(len>0){
249 if(na->nla_type==TASKSTATS_TYPE_AGGR_PID || na->nla_type==TASKSTATS_TYPE_AGGR_TGID){
250 struct nlattr *in=(struct nlattr*)((char*)na+NLA_HDRLEN);
251 int il=na->nla_len-NLA_HDRLEN;
252 while(il>0){
253 if(in->nla_type==TASKSTATS_TYPE_STATS){
254 struct taskstats *ts=(struct taskstats*)((char*)in+NLA_HDRLEN);
255 *cpu_ns=ts->ac_utime*1000ULL+ts->ac_stime*1000ULL;
256 return 0;
257 }
258 il-=NLA_ALIGN(in->nla_len);
259 in=(struct nlattr*)((char*)in+NLA_ALIGN(in->nla_len));
260 }
261 }
262 len-=NLA_ALIGN(na->nla_len);
263 na=(struct nlattr*)((char*)na+NLA_ALIGN(na->nla_len));
264 }
265 return -1;
266}
267
268static void fase_c(void){
269 printf("\n### C. Estadisticas de UNA tarea: taskstats binario contra /proc de texto\n");
270 int s=socket(PF_NETLINK,SOCK_DGRAM|SOCK_CLOEXEC,NETLINK_GENERIC);
271 if(s<0){ printf("taskstats: sin socket genetlink (%s)\n",strerror(errno)); return; }
272 struct sockaddr_nl a; memset(&a,0,sizeof a); a.nl_family=AF_NETLINK; a.nl_pid=getpid();
273 if(bind(s,(struct sockaddr*)&a,sizeof a)<0){ printf("taskstats: bind (%s)\n",strerror(errno)); close(s); return; }
274 int fam=genl_family(s,TASKSTATS_GENL_NAME);
275 if(fam<0){ printf("taskstats: la familia %s no esta (kernel sin TASKSTATS)\n",TASKSTATS_GENL_NAME); close(s); return; }
276 unsigned long long ns=0;
277 if(ts_query(s,fam,getpid(),&ns)<0){ printf("taskstats: la consulta fallo (%s)\n",strerror(errno)); close(s); return; }
278 printf("familia TASKSTATS = %d, cpu de este proceso = %llu ns\n",fam,ns);
279
280 printf("%-44s %10s %10s\n","BENCH (ns por consulta)","min","mediana");
281 const long N=2000;
282 double r[ROUNDS];
283 for(int q=0;q<ROUNDS;q++){
284 double t0=now_ns();
285 for(long i=0;i<N;i++){ unsigned long long v; sink=ts_query(s,fam,getpid(),&v); }
286 r[q]=(now_ns()-t0)/(double)N;
287 }
288 report("taskstats por genetlink (binario)",r,ROUNDS,N);
289
290 for(int q=0;q<ROUNDS;q++){
291 double t0=now_ns();
292 for(long i=0;i<N;i++){
293 int fd=open("/proc/self/stat",O_RDONLY); char b[2048];
294 ssize_t n=read(fd,b,sizeof b-1); close(fd);
295 if(n>0){ b[n]=0; char *p=strrchr(b,')'); for(int f=0;p&&f<12;f++) p=strchr(p+1,' ');
296 sink=p?atol(p+1):0; }
297 }
298 r[q]=(now_ns()-t0)/(double)N;
299 }
300 report("open+read+parse /proc/self/stat",r,ROUNDS,N);
301 close(s);
302}
303
304int main(void){
305 if(geteuid()!=0) fprintf(stderr,"aviso: sin root el connector no se suscribe (CAP_NET_ADMIN)\n");
306 printf("# bench_connector — SDD 25 seccion 9, experimento 1\n");
307 int s=cn_open();
308 if(s<0){ printf("proc connector: no disponible (%s)\n",strerror(errno)); }
309 else { fase_a(s); fase_b(s); close(s); }
310 fase_c();
311 return 0;
312}