-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCloudStorage.txt
3053 lines (2563 loc) · 213 KB
/
CloudStorage.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Escuela Técnica Superior de Ingeniería Informática.
Ingeniero en Informática.
Framework para la creación de infraestructura para provisión de almacenamiento
en la nube como un servicio.
Realizado por:
José Ramón Muñoz Pekkarinen.
Dirigido por:
José Antonio Pérez Castellanos.
Departamento de lenguajes y sistemas Informáticos.
Sevilla, 20 de Noviembre de 2012.
Índice
1.Introducción. 6
2.Planificación. 7
2.1.Estimación del coste. 7
2.2.Método COCOMO. 8
3.Documentación previa. 10
3.1.Cloud Storage. 10
3.2.Almacenamiento en soluciones libres y locales existentes. 10
3.2.1.Amazon S3. 10
3.2.2.OpenNebula. 11
3.2.3.Nimbus. 11
3.2.4.OpenStack. 12
3.2.5.OpenQRM. 12
3.2.6.Eucalyptus. 12
3.2.7.CloudStack. 13
3.2.8.Abiquo Open Source Edition. 13
3.3.Comparación de soluciones. 13
3.4.Swift. 14
3.4.1.Swift Proxy Server. 15
3.4.2.Operaciones en la API. 16
3.4.2.1Identificación. 16
3.4.2.2Listado de contenedores. 16
3.4.2.3Listado de objetos. 17
3.4.2.4Creación de contenedores. 19
3.4.2.5Eliminación de contenedores. 19
3.4.2.6Recuperación de objetos. 19
3.4.2.7Creación, o actualización de objetos. 20
3.4.2.8Copia de objetos. 20
3.4.2.9Eliminación de objetos. 21
3.4.3.Entorno de operación. 21
3.5.Linux. 22
3.5.1.Dispositivos de bloques. 23
3.5.1.1Proceso de una operación. 23
3.5.1.2Estructura genérica de dispositivos de bloques. 24
3.5.2.Dispositivos de red. 25
3.5.3.Network Block Device. 27
3.6.Windows 29
3.6.1.Subsistema de entorno y librerías dinámicas. 31
3.6.2.Ejecutor de Windows. 32
3.6.3.El núcleo. 33
3.6.4.Drivers de dispositivos. 33
3.6.5.Componentes del sistema de entrada/salida. 34
3.6.5.1Gestor de entrada/salida. 34
3.6.5.2Drivers de dispositivos. 35
3.6.5.3Estructura de un driver. 35
3.6.5.4Objetos de drivers y objetos de dispositivos. 36
3.6.5.5Estructuras de ficheros. 37
3.6.5.6Paquetes de peticiones de entrada/salida. 38
3.6.5.7Petición en drivers de una sola capa. 39
3.6.5.8Petición en drivers de múltiples capas. 40
3.6.5.9Infraestructura de drivers en espacio del núcleo(KMDF), 40
3.6.5.10Modelo de entrada/salida de KMDF. 41
3.6.5.11Gestor de dispositivos Plug and Play(PnP). 42
3.6.5.12Carga, inicialización e instalación de drivers. 43
3.6.5.13Gestor de consumo. 45
3.6.6.Subsistema de almacenamiento. 47
3.6.6.1Drivers de disco. 47
3.6.6.2Drivers iSCSI. 49
3.6.6.3Drivers de entrada/salida a través de múltiples caminos. 49
3.6.6.4Objetos de dispositivos de disco. 49
3.6.6.5Discos básicos y discos dinámicos. 50
3.6.6.6Espacio de nombres de volúmenes. 50
3.6.6.6.1Gestor de montaje. 50
3.6.6.6.2Puntos de montaje. 51
3.6.6.6.3Montaje de volúmenes. 52
3.6.6.6.4Operaciones de entrada/salida de volúmenes. 53
3.6.6.7Servicio de discos virtuales. 53
3.6.7.Subsistema de redes. 54
3.6.7.1Modelo OSI. 54
3.6.7.2Componentes de red de Windows. 55
3.6.7.3APIs de red de Windows. 57
3.6.7.3.1Windows Sockets(Winsock). 57
3.6.7.3.2Núcleo Winsock. 58
3.6.7.3.3APIs de acceso web. 59
3.6.7.3.4Otras APIs de red de Windows. 60
3.6.7.3.5Drivers de protocolo. 61
3.6.7.3.6Drivers NDIS. 62
3.6.7.3.7Ensamblado. 63
3.7.Windows vs. Linux. 63
3.8.Swiftness. 64
3.8.1.Bases de diseño. 64
4.Documentación de requisitos. 66
4.1. Participantes. 66
4.1.1.Organizaciones. 66
4.1.2.Participantes. 66
4.2.Objetivos del sistema. 67
4.3.Requisitos del sistema. 68
4.3.1.Requisitos de información. 68
4.3.2.Requisitos funcionales. 71
4.3.2.1Diagrama de casos de uso. 71
4.3.2.2Definición de actores. 72
4.3.2.3Casos de uso. 72
4.3.2.3.1Subsistema de login. 72
4.3.2.3.2Subsistema de almacenamiento. 73
4.3.3.Requisitos no funcionales 79
4.3.4.Reglas de negocio 81
4.4.Matriz de rastreabilidad 82
5.Documentación de análisis. 85
5.1.Modelo estático. 85
5.1.1.Subsistema de Login. 85
5.1.2.Subsistema de Almacenamiento. 85
5.2.Modelo dinámico. 86
5.2.1.Subsistema de Login. 86
5.2.2.Subsistema de almacenamiento. 86
5.3.Modelo funcional. 92
5.3.1.Subsistema de Login. 92
5.3.2.Subsistema de almacenamiento. 93
6.Documentación de diseño. 99
6.1.Arquitectura del sistema. 99
6.2.Patrones de diseño. 100
6.2.1.Fachada 101
6.2.2.Adaptador. 102
6.2.3.Envoltorio. 102
6.3.Diagrama de clases de diseño. 102
6.4.Diseño de datos. 103
6.5.Diseño lógico. 107
7.Glosario. 109
8.Referencias. 111
1 Introducción.
Swiftness es el comienzo del desarrollo de una nueva infraestructura libre, con objeto de proporcionar almacenamiento en la nube como un servicio. Este sistema de almacenamiento puede ser alojado en una nube privada de almacenamiento. El usuario podrá decidir el coste que desea invertir en almacenamiento, pudiendo construir desde nubes de bajo coste, para pequeñas empresas, a nubes de alto coste, disponibilidad y redundancia para grandes corporaciones. Al tratarse de una infraestructura libre, es posible elegir el método de implantación, pudiéndose optar por implantaciones internas, de bajo coste, o contratando los servicios de implantación externos, especialistas en la infraestructura. Estas opciones dotan de flexibilidad al proyecto desarrollado, lo que permite abarcar mercados muy dispares, mejorando la calidad del servicio ofrecido.
La infraestructura deberá estar conformada por una solución de almacenamiento en la nube, de entre las ya existentes, para lo que se ofrecerá un estudio de diversas soluciones, y una comparación por diferentes criterios que nos permita realizar una elección acorde al objetivo perseguido. Posteriormente se estudiarán las plataformas que conforman los clientes de la infraestructura de almacenamiento en la nube, para diseñar y desarrollar sistemas que faciliten la labor de implantación, administración, y uso de esta. Esto conllevará un amplio estudio de los sistemas operativos más comunes en los escritorios y estaciones de trabajo. En concreto, Microsoft Windows y GNU/Linux, con objeto de conocer qué infraestructuras básicas ofrecen, para que el soporte de la infraestructura de almacenamiento se ofrezca desde el mismo sistema operativo, permitiendo al cliente conocer los menos detalles posibles acerca del almacenamiento, sin perder la eficiencia de disponer de un cliente lo más liviano posible. En este sentido adquiere gran importancia la recopilación de información acerca de las infraestructuras ofrecidas por los sistemas operativos, puesto que supondrá una pieza básica para el posterior diseño y desarrollo de los drivers que ofrecerán el soporte deseado y delimitará los objetivos de este proyecto, sin dejar de ofrecer campos de estudio para futuros ciclos de desarrollo que permitan ampliar sus funcionalidades, y mejorar las capacidades ofrecidas en el ciclo inicial. Por tanto, se recogerán estudios, de las infraestructuras de los sistemas operativos, que ofrezcan servicios que pudieran ser no utilizados en este diseño inicial.
Complementaremos este estudio de plataformas con el desarrollo de un driver para el sistema operativo GNU/Linux que sirva como base y ejemplo de los objetivos planteado por el diseño de esta plataforma. Este driver ofrecerá un soporte básico para utilizar la plataforma de almacenamiento, permitiendo que ésta se observe en el cliente como si de un dispositivo local se tratara. Para ello se le dotará de capacidades de conexión con la plataforma, y resolución de operaciones básicas de entrada/salida, es decir, lecturas y escrituras, que el sistema de ficheros realizará al dispositivo. Por tanto, no es objetivo de este proyecto diseñar el sistema de ficheros, ofreciendo la flexibilidad al usuario de utilizar el que desee. Sin embargo, sí es uno de nuestros objetivos traducir las peticiones realizadas por el sistema de ficheros, a las comunicaciones de red requeridas por el servidor para que éste devuelva la respuesta requerida, y, posteriormente, traducir las respuestas recibidas para que el sistema de ficheros entienda qué es lo que el servidor ofrece.
2 Planificación.
Con el objetivo de garantizar la calidad del servicio ofrecido con el presente proyecto, realizaremos una estimación del tiempo y esfuerzo requerido para llevar a cabo las distintas tareas que lo componen. Cada tarea recibirá una estimación inicial, previa a la realización del objetivo, y una estimación final que se corresponde con el tiempo utilizado para realizar la tarea durante el ejercicio del proyecto. Se calculará el error relativo de dichas estimaciones para contrastar si dichas estimaciones se ajustan a lo que realmente se necesitó.
La distintas estimaciones se recogen en la siguiente tabla, en la que se estima que trabaja una persona, 8 horas al día:
Tarea
Estimación Inicial
Estimación Final
Error Relativo
Introducción.
1 día
1 día
2.12%
Planificación
1 día
1 día
2.12%
Documentación de nubes de almacenamiento.
7 días
10 días
39.83%
Documentación de S.O. GNU/Linux.
20 días
18 días
-11.91%
Documentación de S.O. Windows.
30 días
27 días
-11.91%
Documentación de requisitos.
7 días
7 días
2.12%
Documentación de análisis.
7 días
9 días
2.12%
Documentación de diseño.
7 días
6 días
-16.10%
Implementación.
7 días
10 días
39.81%
Pruebas.
5 días
5 días
2.12%
TOTAL
92
94
2.12%
Error relativo medio
5.03%
2.1 Estimación del coste.
Para determinar el coste del proyecto que nos concierne, acudiremos al uso de la conocida métrica del número de lineas de código (LDC). Ésta métrica propone el coste de un proyecto basándose en su tamaño, y es de las métricas más utilizadas en la ingeniería del software. Dentro de ella se entienden por lineas de código, las lineas útiles, quedando excluidas las lineas en blanco y los comentarios. Su clasificación se recoge en la siguiente tabla:
Categoría
Programadores
Duración
LDC
Ejemplo
Trivial
1
0-4 semanas
< 1K
Utilidad de ordenación
Pequeño
1
1-6 meses
1K -3K
Biblioteca de funciones
Medio
2-5
0.5-2 años
3K-50K
Compilador de C
Grande
5-20
2-3 años
50K-100K
S.O. pequeño
Muy grande
100-1000
4-5 años
100K-1M
S.O. Grande
Gigante
1000-5000
5-10 años
>1M
Sistema de distribución
La aplicación que se provee con dicho proyecto se aproxima a las 1000 líneas de código, luego, se encuentra en la clasificación de un proyecto pequeño. Esto se debe a que, como comentábamos anteriormente, una parte importante dentro de éste es la realización del estudio detallado de las nubes de almacenamiento y los sistemas operativos en los que se ofrecerá el soporte de dicha nube.
2.2 Método COCOMO.
Para tener una estimación más elaborada del coste del proyecto, se usará el método COCOMO (Constructive Cost Model). Esta metodología de la ingeniería del software hace uso de 3 modelos distintos: básico, medio y avanzado. El modelo se selecciona en función del número de variables que definen nuestro problema, siendo el modelo básico el que menos variables utiliza, y el modelo avanzado el que más. Es posible aplicarlo a los siguientes tres tipos de software:
Orgánico: son proyectos con un tamaño menor a 50000 líneas de código, en los que el equipo de desarrollo tiene experiencia, y se utiliza un entorno estable de desarrollo.
Semiacoplado: proyectos intermedios en complejidad y tamaño, en los que la experiencia del equipo es variable, y sus restricciones son de complejidad intermedia.
Empotrado: se tratan de proyectos complejos, en los que la experiencia aportada es poca o nula, en un entorno de innovación técnica. Sus requisitos son muy restrictivos y de gran volatilidad.
Para nuestro modelo, ya que estimaremos el coste haciendo uso únicamente del tamaño en líneas de código, el modelo básico nos ofrecerá una buena aproximación del coste de nuestro proyecto.
El tipo de proyecto, dado que la variable en que nos apoyamos es el número de líneas de código, se corresponde con un sistema orgánico, y será considerado como tal. Sin embargo, conviene recordar que se trata de un proyecto con requisitos muy restrictivos, y en los que el equipo de desarrollo carece de experiencia alguna.
La ecuación del esfuerzo es la siguiente:
personas/mes.
KLDC se corresponde con el número de líneas de código en millares.
La ecuación del tiempo de desarrollo es la siguiente:
Los coeficientes a, b, c y d, se hallan de forma empírica, y sus valores quedan recogidos en la siguiente tabla:
Proyecto
A
B
C
D
Orgánico
2.4
1.05
2.5
0.38
Semiacoplado
3
1.12
2.5
0.35
Empotrado
3.6
1.2
2.5
0.32
El esfuerzo resultante es
Su tiempo sería:
De estos cálculos puede deducirse el número de personas requerido para realizar el proyecto, siendo este . Luego el proyecto es realizable por una persona en un tiempo aproximado de tres meses y medio.
3 Documentación previa.
3.1 Cloud Storage.
El Cloud Storage, o almacenamiento en la nube, es un modelo de almacenamiento basado en redes en el que los datos son almacenados en piscinas virtuales de almacenamiento.
En las arquitecturas de almacenamiento en la nube, se persiguen cuatro objetivos, la agilidad, o capacidad de mover los datos al sitio adecuado para mejorar su disponibilidad, la escalabilidad, o capacidad de manejar crecientes cargas de trabajo, o de adaptarse para ser capaces de soportarlas, la elasticidad, o la capacidad de escalarse sin limites razonables, y la capacidad de ser multiusuario.
El Cloud Storage debe estar formado por servicios distribuidos que se comporten como un conjunto, debe ser tolerante a fallos a través de la redundancia de datos, ser duradero, sirviéndose de la creación de versiones de los documentos alojados, y, finalmente, debe ser consistente entre las diferentes versiones almacenadas.
Las principales preocupaciones que se plantean en estos modelos de almacenamiento son la seguridad ante posibles accesos indebidos, la estabilidad de la empresa proveedora, la accesibilidad de la información, y los costes hardware de la solución utilizada.
Actualmente, existen dos tipos de instalaciones de Cloud Storage, las realizadas en servidores externos, y las instalaciones locales que el administrador puede acomodar a sus necesidades.
En servidores externos, la compañías que ofrecen este servicio establecen una piscina de servidores virtuales en los que se aloja dicho almacenamiento y los pone a disposición del usuario. Este tipo de instalaciones goza de las mayores ventajas del Cloud Storage, que se apoyan en la disposición de grandes servidores de almacenamiento, y la delegación de la tarea de la administración y responsabilidades a la compañía encargada de ofrecer el servicio.
En las instalaciones locales, el usuario debe instalar la plataforma de almacenamiento en la nube, adaptarla a sus necesidades, y administrarla adecuadamente con el fin que persigue. En el proyecto que nos ocupa estudiaremos en mayor profundidad las soluciones locales.
3.2 Almacenamiento en soluciones libres y locales existentes.
3.2.1 Amazon S3.
La plataforma Amazon S3 no se trata expresamente de una plataforma libre, ni de instalación local, puesto que sólo se ofrecen las distintas interfaces de comunicación de esta. Aun así es necesario estudiarla, debido a que algunas soluciones basan su almacenamiento en esta plataforma.
La Amazon Simple Storage Service tiene como objetivo ser escalable, tener alta disponibilidad y baja latencia, todo ello a un bajo coste. Dispone de tres tipos de interfaces distintas, REST (Representational State Transfer), SOAP (Simple Object Access Protocol) y BitTorrent. La primera se trata de una interfaz para sistemas distribuidos, la más adoptada en la World Wide Web. La segunda es una interfaz que cumple el mismo propósito que REST, pero basándose en tecnología XML. La tercera es una de las conocidas tecnologías P2P existentes.
Admite objetos de un tamaño inferior a 5 terabytes de información y los organiza en cubos. Para cada cubo, y objeto, dentro de éste, se proporciona una lista de control de acceso que permite al usuario disponer de su información. A cada objeto en un cubo puede accediderse directamente desde internet. Además, cada objeto es una semilla de la red BitTorrent que puede ser utilizada para reducir el ancho de banda utilizado durante la descarga.
Puesto que el principal uso de los cubos es almacenar páginas web, cada cubo puede ser configurado para almacenar los registros de acceso en un cubo gemelo, o réplica.
Esta solución es capaz de alojar maquinas virtuales que se pueden ejecutar a través de sus interfaces expuestas, y admite redundancia distribuida en su cluster de almacenamiento.
Debido a su condición de no tener instalación local, no es posible saber sobre que tipo de host funciona dicha solución.
3.2.2 OpenNebula.
Aunque la plataforma de Cloud Computing de OpenNebula es compatible con la interfaz Amazon EC2 Query Interface, lo que le ofrece a su vez compatibilidad con la plataforma de almacenamiento Amazon S3, dicha plataforma goza de un subsistema de almacenamiento propio.
El subsistema está ideado para almacenar máquinas virtuales únicamente. Dicha plataforma se compone de una serie de drivers divididos en dos tipos, data store drivers, o drivers de almacenamiento de datos, y transfer manager drivers, o drivers de gestión de transferencia. Los primeros se encargan de ofrecer las funcionalidades de creación, eliminación y modificación de las máquinas virtuales. Los segundos se encargan de realizar las transferencias a otras localizaciones, clonación y enlace.
Los drivers de transferencia proveen a esta solución de una interfaz REST, y no es posible separar los subsistemas de comunicación de los de gestión de almacenamiento y operación de esta nube. No se especifica que disponga de persistencia de datos replicándolos a lo largo del cluster formado por la nube. Los tipos de host donde pueden ser alojada esta solución deben ser servidores Linux.
3.2.3 Nimbus.
La plataforma Nimbus utiliza una implementación propia y libre de la API REST (Representational State Transfer) de Amazon S3, llamada Cumulus, para su almacenamiento. Las interfaces que ofrece dicha implementación son de tipo REST y SOAP, y no es posible separar la interfaz de comunicación del almacenamiento subyacente. Su almacenamiento, al igual que OpenNebula está basado en máquinas virtuales que gestionan sus propios discos, no siendo posible definir un almacenamiento sin sistema operativo. Esta solución permite, sin embargo, realizar replicación distribuida de datos para garantizar la persistencia. Los host que soportan esta solución deben ser servidores Linux.
3.2.4 OpenStack.
Dicha solución se divide en tres partes bien diferenciadas para ofrecer una nube personal. Éstas son Nova, para la ejecución de software, Swift, para el almacenamiento, y Glance, para la gestión de imágenes virtuales. Esto le ofrece la capacidad de poder instalar sólo el componente necesario para las necesidades del usuario. Swift es capaz de almacenar objetos o bloques indistintamente, permitiendo no sólo el almacenamiento de imágenes virtuales, sino de cualquier tipo de información.
Esta solución es escalable, distribuida, admite redundancia de datos, y propone al usuario una API de programación. Dicha API es compatible con las soluciones de almacenamiento masivo NetApp, Nexenta, SolidFire y Amazon S3. Debido a esto, admite comunicaciones a través de una interfaz REST o SOAP. El soporte de bloques permite que cualquier dispositivo de bloques(por ejemplo un disco duro, una cinta magnética, o una imagen virtual) pueda añadirse al sistema de almacenamiento distribuido de forma transparente.
Su organización se basa en clústeres de almacenamiento, y cada objeto se distribuye entre diferentes nodos de la red. El sistema garantiza la persistencia de bloques y capacidad de realizar instantáneas(snapshots) del almacenamiento para labores de salvaguardas.
Una vez más el tipo de servidor que es capaz de alojarlo es un servidor Linux.
3.2.5 OpenQRM.
En el caso de OpenQRM nos encontramos con una solución propia, basada en el sistema de plugins para la plataforma. Dichos plugins se encargan de dar soporte por separado a los diversos tipos de almacenamientos reales en las distintas máquinas. Para ello, proveen al usuario de una interfaz SOAP. Su tipo de almacenamiento es basado en máquinas virtuales y no admite replicación de datos a lo largo del cluster.
Entre ellos podemos encontrarnos los siguientes plugins, que nos dan una idea del tipo de almacenamiento tratado: netapp-storage, nfs-storage, local-storage, iscsi-storage, lvm-storage, xen-storage y kvm-storage.
Dicha solución debe ser instalada sobre un servidor Linux.
3.2.6 Eucalyptus.
Entre los distintos componentes de esta solución de Cloud Computing se encuentra el Storage Controller, o controlador de almacenamiento. Dicho componente implementa el sistema de almacenamiento Amazon EBS, Elastic Block Store, el cual provee de dispositivos de bloques brutos a la plataforma, siguiendo la estructura detallada en la plataforma Amazon S3. Esta implementación le permite utilizar las interfaces REST, SOAP y Bittorrent para manejar el almacenamiento, y en este caso es posible separar el subsistema de comunicación del de almacenamiento en servidores distintos.
Los hosts que soportan dicha solución deben ser servidores Linux.
3.2.7 CloudStack.
Esta plataforma se nutre de la solución de almacenamiento de OpenStack, Swift, estudiada anteriormente. Por tanto, ofrece interfaces REST y SOAP para su comunicación, cualquier tipo de almacenamiento, almacenamiento distribuido a lo largo del cluster, y, a diferencia de OpenStack, es capaz de instalarse sin sistema operativo servidor.
3.2.8 Abiquo Open Source Edition.
En esta plataforma nos encontramos con que el almacenamiento es independiente al resto de la plataforma, ofreciendo solo almacenamiento virtual de dos tipos: asistido, o genérico iSCSI. En el caso del almacenamiento asistido la plataforma ofrece almacenamiento como servicio directamente al usuario. En el caso del almacenamiento genérico iSCSI, el usuario dispone de un almacenamiento preconfigurado por el administrador del sistema, habitualmente una máquina virtual. Para ello se apoya en un conjunto de plugins, al igual que OpenQRM, que dan soporte a los siguientes tipos de almacenamiento: LVM y iSCSI Linux, Nexenta, NetApp. Por otra parte se ofrece una API de desarrollo para facilitar el soporte de otras plataformas como Dell Equallogic, IBM Volume Manager.
Esta solución solo ofrece una interfaz REST al usuario, y no ofrece replicación distribuida de forma automática. En este caso no se requiere de ningún sistema operativo para alojar la nube.
3.3 Comparación de soluciones.
La mayor parte de las soluciones estudiadas implementan al menos alguna de las interfaces de comunicación propuestas por la nube de Amazon, además de compartir su tipo de almacenamiento, y, al menos, ofrecen al usuario de almacenamiento basado en máquinas virtuales. Sin embargo, no todas ofrecen la oportunidad de realizar una instalación minimalista orientada al almacenamiento. Exceptuando la nube de Abiquo, y por razones obvias, la solución de Amazon, todas deben ser alojadas en servidores Linux, no soportándose la instalación nativa sobre un servidor, o la instalación sobre servidores Windows.
De este amplio abanico de soluciones es necesario destacar el soporte de las tres interfaces habituales por parte de Eucalyptus, y su soporte para distintos tipos de almacenamiento, admitiendo flexibilidad de instalación. Sin embargo, esta poderosa nube está orientada a ofrecer todos los servicios disponibles en cualquier nube habitual(computación, virtualización y almacenamiento), y no es especialmente cómoda para realizar una instalación minimalista, orientada al almacenamiento.
La solución Nimbus, a través de su servidor Cumulus, ofrece una imagen de sencillez si el objetivo perseguido es configurar una nube de almacenamiento, sin embargo su fuerte orientación a requerir máquinas virtuales con sistemas operativos propios hacen que la solución pierda atractivo. OpenNebula y OpenQRM, disponen de características similares, conformando las soluciones más rígidas de todas, pero a la vez las más sencillas de administrar. Además, su implementación basada en drivers o plugins asegura facilidad de desarrollo de código y alta modularidad.
Las soluciones Openstack y Cloudstack ofrecen alta flexibilidad de instalación, una modularización aceptable, y por tanto, facilidad de mantenimiento de código, así como la posibilidad de hacer instalaciones minimalistas orientadas a cada uno de sus servicios por separado. Esta última característica las convierten en plataformas adecuadas para cualquier tipo de desarrollo sobre cualquiera de sus servicios.
Las características de las distintas plataformas se resumen en la siguiente tabla:
Característica / Nube
Amazon S3
OpenNebula
Nimbus
OpenStack
OpenQRM
Eucalyptus
Cloudstack
Abiquo O.S.E.
APIs soportadas
REST
Sí
Sí
Sí
Sí
No
Sí
Sí
Sí
SOAP
Sí
No
Sí
Sí
Sí
Sí
Sí
No
Bittorrent
Sí
No
No
No
No
Sí
No
No
Comunicación independiente de almacenamiento
No
Sí
No
Sí
No
Sí
Sí
Sí
Tipo almacenamiento
M.V.
Sí
Sí
Sí
Sí
Sí
Sí
Sí
Sí
Puro
Sí
No
No
Sí
No
Sí
Sí
Sí
Cluster de replicación
Sí
No
Sí
Sí
No
Sí
Sí
No
Host
No requerido
?
No
No
No
No
No
Sí
Sí
Linux
?
Sí
Sí
Sí
Sí
Sí
Sí
No
Windows
?
No
No
No
No
No
No
No
3.4 Swift.
Este componente de la nube propuesta por Openstack está formado por cuatro servidores que se comunican para ofrecer el servicio propuesto. Éstos son, un servidor de cuentas, un servidor de contenedores, un servidor de objetos y un servidor proxy para distribuir las peticiones entre el resto de servidores. De esta forma, para realizar cualquier transacción, el cliente deberá solicitar, al servidor de cuentas, acceso a la plataforma. Validado su acceso, dispondrá de acceso al contenedor asociado a su cuenta, y a los objetos asociados a este contenedor, cada uno de ellos gestionado por su servidor correspondiente. Para realizar cualquier petición, el cliente debe comunicarse con el servidor proxy, que ejerce de fachada aislando al resto de servidores. La organización de éstos sería la siguiente:
Para el objetivo de este proyecto, todos los servidores pueden ser tratados como una caja negra que se dedica al almacenamiento y distribución de datos, excepto el servidor proxy, cuya API estudiaremos en mayor profundidad.
3.4.1 Swift Proxy Server.
La comunicación con este componente, se basa en el envío de peticiones HTTP. Dichas peticiones se gestionan mediante el uso de la operación GET, y deben contener unas cabeceras específicas para reconocer los distintos tipos de peticiones. Éstas disponen de una serie de restricciones detalladas a continuación:
El numero máximo de cabeceras por petición es de 90.
La máxima longitud de las cabeceras es 4096 bytes.
Cada línea de petición no debe tener más de 8192 bytes.
Una petición no debe exceder el tamaño de 5 gigabytes.
El nombre de un contenedor no puede ser mayor de 256 bytes.
El nombre de un objeto no puede ser mayor que 1024 bytes.
La operaciones posibles sobre el almacenamiento se dividen entre los tres servidores de gestión de éste. Detallamos a continuación las operaciones relevantes:
Servidor de cuentas.
Listado de contenedores.
Servidor de contenedores.
Listado de objetos en el contenedor.
Creación de contenedores.
Eliminación de contenedores.
Servidor de objetos.
Recuperación de objetos.
Creación/actualización de objetos.
Copia de objetos.
Eliminación de objetos.
3.4.2 Operaciones en la API.
Como ya adelantábamos anteriormente, para interactuar con el servidor proxy se utiliza una interfaz REST, es decir, el servidor espera peticiones HTTP para servir cualquier tipo de fichero disponible, y realizar el control de acceso al almacenamiento.
3.4.2.1 Identificación.
Esta operación es, por razones de seguridad, la primera a realizar de todas las posibles. Para ello hace falta proveer al servidor de un usuario válido y su contraseña a través de la operación GET de la versión 1.1 de HTTP. El servidor nos devolverá un identificador único necesario para realizar cualquier posterior operación. La solicitud es la siguiente:
GET /<api version> HTTP/1.1
Host: <Servidor proxy>
X-Auth-User: <usuario>
X-Auth-Key: <contraseña>
La respuesta tiene la siguiente forma:
HTTP/1.1 204 No Content
Date: <fecha>
Server: <servidor web>
X-Storage-Url: <dirección para posteriores accesos>
X-Auth-Token: <identificador para posteriores accesos>
Content-Length: 0
Content-Type: text/plain; charset=UTF-8
3.4.2.2 Listado de contenedores.
La operación de listado de contenedores no requiere ningún parámetro en especial, más que el identificador de sesión obtenido durante la identificación. Sin embargo, existen un par de variables de configuración de interés general para dicha operación. Las variables se concatenan a la dirección solicitada en la operación GET, separando el listado de variables de la dirección por un interrogante('?'). Dichas variables sirven para describir el formato en que se recibirá el listado(siendo válidos los valores format=json y format=xml), el límite de contenedores que se quiere recibir, configurando la variable limit=N, y la coincidencia desde la cual se quiere comenzar el listado con la variable marker.
GET /<api version>/<account>?var1=val1& … & varN=valN HTTP/1.1
Host: <dirección del servidor>
X-Auth-Token: <identificador de sesión>
Un ejemplo de respuesta en formato JSON es el siguiente:
HTTP/1.1 200 OK
Date: Tue, 25 Nov 2008 19:39:13 GMT
Server: Apache
Content-Type: application/json; charset=utf-8
[
{"name":"test_container_1", "count":2, "bytes":78},
{"name":"test_container_2", "count":1, "bytes":17}
]
En formato XML la respuesta sería de la siguiente forma:
HTTP/1.1 200 OK
Date: Tue, 25 Nov 2008 19:42:35 GMT
Server: Apache
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<account name="MichaelBarton">
<container>
<name>test_container_1</name>
<count>2</count>
<bytes>78</bytes>
</container>
<container>
<name>test_container_2</name>
<count>1</count>
<bytes>17</bytes>
</container>
</account>
3.4.2.3 Listado de objetos.
En el caso de los listados de objetos, se dispone de algunas variables de configuración adicionales, además de las ya descritas para los listados de contenedores. Estas nuevas variables son: end_marker, para devolver un listado terminado en el valor especificado, prefix, para ofrecer un prefijo que preceda al nombre del objeto y delimiter, para mostrar las ocurrencias listadas hasta el delimitador, desechando el resto de la dirección. Un ejemplo de uso de delimitador es aquél en el que sólo queremos listar los directorios existentes en la raíz del almacenamiento, para lo cual solamente requerimos utilizar el delimitador '/' para obtener el resultado deseado.
El formato de la petición es el siguiente:
GET /<api version>/<account>/<container>[?parm=value] HTTP/1.1
Host: <servidor>
X-Auth-Token: <identificador de sesión>
Un ejemplo de respuesta en formato JSON:
HTTP/1.1 200 OK
Date: Tue, 25 Nov 2008 19:39:13 GMT
Server: Apache
Content-Length: 387
Content-Type: application/json; charset=utf-8
[
{"name":"test_obj_1",
"hash":"4281c348eaf83e70ddce0e07221c3d28",
"bytes":14,
"content_type":"application\/octet-stream",
"last_modified":"2009-02-03T05:26:32.612278"},
{"name":"test_obj_2",
"hash":"b039efe731ad111bc1b0ef221c3849d0",
"bytes":64,
"content_type":"application\/octet-stream",
"last_modified":"2009-02-03T05:26:32.612278"},
]
En formato XML el servidor respondería lo siguiente:
HTTP/1.1 200 OK
Date: Tue, 25 Nov 2008 19:42:35 GMT
Server: Apache
Content-Length: 643
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<container name="test_container_1">
<object>
<name>test_object_1</name>
<hash>4281c348eaf83e70ddce0e07221c3d28</hash>
<bytes>14</bytes>
<content_type>application/octet-stream</content_type>
<last_modified>2009-02-03T05:26:32.612278</last_modified>
</object>
<object>
<name>test_object_2</name>
<hash>b039efe731ad111bc1b0ef221c3849d0</hash>
<bytes>64</bytes>
<content_type>application/octet-stream</content_type>
<last_modified>2009-02-03T05:26:32.612278</last_modified>
</object>
</container>
3.4.2.4 Creación de contenedores.
Los contenedores, no son más que compartimentos en los que almacenar los objetos. Sin embargo, sus nombres deben cumplir las restricciones de no tener una longitud mayor de 256 caracteres, y no contener el carácter '/'. Un ejemplo de petición de creación de un contenedor es el siguiente:
PUT /<api version>/<account>/<container> HTTP/1.1
Host: <servidor>
X-Auth-Token: <identificador de sesión>
La respuesta en este caso:
HTTP/1.1 201 Created
Date: Thu, 07 Jun 2010 18:50:19 GMT
Server: Apache
Content-Type: text/plain; charset=UTF-8
3.4.2.5 Eliminación de contenedores.
Para la eliminación de un contenedor, es suficiente con la siguiente petición:
DELETE /<api version>/<account>/<container> HTTP/1.1
Host: <server>
X-Auth-Token: <identificador de sesión>
Sin embargo debe suceder que el contenedor esté vacío para que éste pueda ser eliminado. Su borrado es permanente. Su respuesta pudiera ser la siguiente:
HTTP/1.1 204 No Content
Date: Thu, 07 Jun 2010 18:57:07 GMT
Server: Apache
Content-Length: 0
Content-Type: text/plain; charset=UTF-8
3.4.2.6 Recuperación de objetos.
Aunque habitualmente se utiliza la operación GET para recuperar objetos del almacenamiento masivo, es posible utilizar las directivas If-Match, If-None-Match, If-Modified-Since e If-Unmodified-Since definidas en el protocolo RFC2616. Como sus nombres indican, su funcionalidad es hacer condicional la recuperación del objeto en cuestión, definiendo si se encuentra un objeto que coincida en nombre, si no lo hay, o si ha sido modificado desde una fecha determinada.
Existe también un soporte básico para recuperar rangos de memoria dentro del objeto, permitiendo recuperar sólo partes de éste si no se requiere el objeto completo.
Su petición es la siguiente:
GET /<api version>/<account>/<container>/<object> HTTP/1.1
Host: <servidor>
X-Auth-Token: <identificador de sesión>
Su posible respuesta correspondiente sería:
HTTP/1.1 200 Ok
Date: Wed, 11 Jul 2010 19:37:41 GMT
Server: Apache
Last-Modified: Fri, 12 Jun 2010 13:40:18 GMT
ETag: b0dffe8254d152d8fd28f3c5e0404a10
Content-type: text/html
Content-Length: 512000
[ ... ]
3.4.2.7 Creación, o actualización de objetos.
Es posible, y recomendable, que para la creación o actualización de objetos se utilicen sumas MD5 que ayuden a verificar la integridad de éste. Dicha funcionalidad está soportada a través del envío de una cabecera especial llamada Etag. Independientemente de si esta cabecera es incluida en la creación del objeto, al recuperarlo, siempre se devolverá el valor de la suma, para que el usuario pueda comprobar su integridad. Si se desea que el objeto expire en un tiempo determinado, o en una fecha, es posible añadir las cabeceras X-Delete-At y X-Delete-After. Su petición tiene la siguiente forma:
PUT /<api version>/<account>/<container>/<object> HTTP/1.1
Host: <servidor>
X-Auth-Token: <identificador de sesión>
ETag: <suma MD5>
Content-Length: <longitud en bytes>
X-Object-Meta-PIN: 1234
[ ... ]
Su posible respuesta sería:
HTTP/1.1 201 Created
Date: Thu, 07 Jun 2010 18:57:07 GMT
Server: Apache
ETag: d9f5eb4bba4e2f2f046e54611bc8196b
Content-Length: 0
Content-Type: text/plain; charset=UTF-8
3.4.2.8 Copia de objetos.
De cometerse un error con el nombre del objeto, al subirlo, o sencillamente querer cambiar su nombre, el objeto debería eliminarse y subirse de nuevo, produciendo sobrecarga en las comunicaciones. Esto se evita mediante la copia en servidor de objetos. Esta copia puede realizarse de dos formas: a través de la cabecera X-Copy-From, donde se especificará el contenedor y el objeto que se desea copiar, o mediante la operación COPY. Detallamos sus formas a continuación:
PUT /<api version>/<account>/<container>/<destobject> HTTP/1.1
Host: <storage URL>
X-Auth-Token: <some-auth-token>
X-Copy-From: /<container>/<sourceobject>
Content-Length: 0
COPY /<api version>/<account>/<container>/<sourceobject> HTTP/1.1
Host: <storage URL>
X-Auth-Token: <some-auth-token>
Destination: /<container>/<destobject>
3.4.2.9 Eliminación de objetos.
La eliminación de objetos es inmediata y permanente, toda operación sobre el objeto después de la operación DELETE, devolverá el conocido error 404 de HTTP, objeto no encontrado. Es posible programar la eliminación mediante el uso de las cabeceras X-Delete-At y X-Delete-After. La petición debe tener la siguiente forma:
DELETE /<api version>/<account>/<container>/<object> HTTP/1.1
Host: <servidor>
X-Auth-Token: <identificador de sesión>
Su respuesta se asemejará a la detallada a continuación:
HTTP/1.1 204 No Content
Date: Thu, 07 Jun 2010 20:59:39 GMT
Server: Apache
Content-Type: text/plain; charset=UTF-8
3.4.3 Entorno de operación.
Para poder comprobar que nuestros drivers funcionan correctamente, es necesario preparar un servidor Openstack Swift donde poder realizar las conexiones pertinentes. Para este proyecto, hemos elegido usar una máquina virtual utilizando la tecnología de virtualización KVM, propia del núcleo de Linux. La máquina ha sido dotada de un sistema operativo Debian GNU/Linux en su versión de pruebas(actualmente Debian GNU/Linux Wheezy), y dispone de dirección IP propia dentro de la red gracias a la compatibilidad de KVM con los puentes virtuales que ofrece Linux. Además del software básico que se instala automáticamente con la distribución, se le ha añadido un servidor OpenSSH para realizar conexiones seguras a la máquina y los paquetes, incluidos en los repositorios de la distribución, de Openstack Swift. De esta forma puede ejecutarse la máquina virtual como si de un servidor del sistema real se tratara, y podemos realizar cualquier prueba pertinente de nuestros drivers. Para la configuración del servidor Swift, hemos seguido las instrucciones detalladas en la documentación de Openstack SAIO(Swift All In One).
3.5 Linux.
En el conocido sistema operativo libre, creado por Linus Torvalds y publicado en el año 1991, los programas en espacio de usuario deben comunicarse con el núcleo a través de módulos que permitan la gestión de dispositivos en el sistema. Dichos módulos representan tres tipos de dispositivos: de caracteres, de bloques, y de red.
Los dispositivos de caracteres son aquellos que se comunican mediante flujos de caracteres secuenciales. Un ejemplo sencillo es el teclado, al que puede accederse, y la información transmitida es un flujo de caracteres secuencial: si tecleas una palabra, ésta se tratará en su estricto orden, y no en otro distinto. Los dispositivos de bloque suelen ser dispositivos de almacenamiento masivo y se estructuran en bloques o porciones a los que se accede en secuencias de operaciones, y a sus bloques no necesariamente se accede de manera secuencial. Por último, los dispositivos de red se encargan de cualquier tipo de comunicación con el exterior del sistema a través de sus interfaces de red, como las tarjetas Ethernet, modems, etc.
Los dispositivos representados no tienen la obligación de ser dispositivos reales, pudiendo permitirse la existencia de dispositivos virtuales que resuelvan un tipo de tarea determinada. Un ejemplo claro de dispositivo virtual es el dispositivo de generación de números aleatorios(al que se accede desde el fichero /dev/random), que no requiere de ningún hardware especifico para desempeñar su función.
La intención principal de Swiftness es proveer de un dispositivo de bloques que sea capaz de acceder al servidor Swift de Openstack, como si de un dispositivo local se tratara. Esto conlleva la clara diferencia de que, si detrás de un driver de dispositivo de bloques solemos encontrar dispositivos de almacenamiento físicos(como por ejemplo, un disco duro, una cinta magnética o un CD-ROM), en este caso nos encontraremos con una interfaz de red que debe saber comunicarse con el servidor.
Cabría esperar que la labor de Swiftness fuera la de la traducción de las peticiones de bloques a directivas TCP. Sin embargo dicha labor ya se encuentra cubierta por el módulo NBD(Network Block Device) que estudiaremos más adelante. Con esto, la labor de Swiftness se reduce a implementar paquetes con las cabeceras adecuadas para que el servidor Swift pueda ser gobernado por el módulo NBD.
3.5.1 Dispositivos de bloques.
Los drivers dispositivos de bloques, dentro del código fuente de Linux, se alojan en el directorio “drivers/block”, y las interfaces que deben cumplir en el directorio “include/linux”. Como antes adelantábamos, dichos dispositivos disponen de grandes cantidades de información, dividida en bloques a los que no se accederá secuencialmente y es este hecho el que los diferencia de los dispositivos de caracteres. Debido a que tendremos que navegar a lo largo del dispositivo para acceder a la información, estos dispositivos ganan complejidad. La naturaleza de dichos dispositivos hacen al sistema altamente sensible a su disponibilidad.
El tamaño de un sector depende del dispositivo en cuestión, y es la unidad mínima fundamental de un dispositivo de bloques. Aunque muchos dispositivos de bloques dispongan de un tamaño de sector de 512 bytes, esto no quiere decir que sea un tamaño estándar. El sistema operativo debe cumplir con ciertas restricciones a la hora de acceder a un dispositivo de bloque. Debe acceder a la información en bloques de tamaño múltiplo del tamaño de sector, el tamaño de este bloque, deberá ser potencia de dos(restricción que suele aplicarse también al tamaño de sector), y el tamaño de bloque no puede ser mayor que el tamaño de una página de información en memoria principal. Los tamaños más habituales son 512 bytes, 1 kilobytes, y 4 kilobytes.
3.5.1.1 Proceso de una operación.
La jerarquía de subsistemas para acceder a un dispositivo de bloques, desde que una simple operación de lectura es enviada desde el espacio de usuario, hasta que llega al dispositivo es amplia. Ésta contempla desde el sistema de ficheros virtual, hasta el driver del dispositivo de bloques, pasando por las caches de discos, los distintos sistemas de ficheros, conformando la capa de direccionamiento del sistema, la capa genérica del dispositivo de bloques, y el planificador de entrada salida. Debido a esto, resulta interesante estudiar qué sucede, paso por paso, cuando una operación sobre el dispositivo está procesándose.
Suponemos que se ha llamado a la rutina de servicio read() para hacer un acceso de lectura. Este acceso de lectura provoca que el sistema active la función más adecuada dentro del sistema de ficheros virtual para procesarla. Esta función recibirá el descriptor del fichero abierto a leer y un desplazamiento dentro del fichero, para describir el bloque que se quiere leer. El sistema de ficheros virtual, debe determinar si los datos están ya disponibles en memoria principal, y cómo se realizará el acceso.
De requerir acceder al dispositivo de bloques, el sistema de ficheros virtual acudirá a la capa de direccionamiento. En esta capa se ejecutarán dos tareas principales. La primera será determinar el tamaño de bloque, para calcular el desplazamiento dentro del fichero en función de este tamaño. Posteriormente se invoca una función del sistema de ficheros real que determine el nodo del fichero y su posición dentro del disco.
Tras estas operaciones, el núcleo accederá a la capa genérica de dispositivos de bloque para comenzar la operación de entrada/salida. Aquí se crearán e inicializarán las estructuras necesarias que se ofrecerán al planificador de accesos.
El planificador encola y ordena los accesos a los distintos bloques dentro del dispositivo con el fin de optimizar el acceso al dispositivo de bloques real, ya que, de una sola operación en él, accederemos a diversos bloques, y, debido a la lentitud del acceso a este tipo de dispositivos, es necesario asegurarse de que el número de accesos se reduzca todo lo posible.
Finalmente, las peticiones se ofrecen al driver del dispositivo de bloques, que ejecutará la operación real. Todo este largo proceso puede verse resumido en la siguiente figura, donde se observa la jerarquía completa de subsistemas.
3.5.1.2 Estructura genérica de dispositivos de bloques.
Cada vez que un bloque es alojado en memoria principal, éste es asociado a un buffer. Un buffer es un objeto que representa un bloque en memoria, y, dado que el núcleo requiere más información que ésta, cada buffer se asociará a un descriptor llamado buffer_head. Esta estructura almacenará datos como el estado del bloque, su página asociada, su tamaño, un puntero al comienzo de los datos, y el dispositivo al que pertenece, entre otros detalles. Puede consultarse la estructura en el fichero “include/linux/buffer_head.h”. Aunque mucha de esta información es necesaria para el núcleo, no deja de ser una sobrecarga y un consumo de espacio en memoria.
Otra estructura de importancia, es aquella que almacena la información de una operación de entrada/salida. Ésta es la estructura bio (block input/output, detallada en el fichero “include/linux/bio.h”). Las operaciones descritas por esta estructura operan sobre segmentos, o porciones de un buffer, que no tienen la obligación de estar alojadas de forma contigua en memoria principal. De esta forma, el núcleo puede operar con un sector repartido a lo largo de toda la memoria. La información de esta estructura es mayoritariamente informativa, y sus campos mas relevantes con bi_vcnt, bi_idx y bi_io_vec. Estas estructuras se organizan en un vector de estructuras llamadas bio_vec. El campo bi_io_vec, almacena la dirección de su vector asociado, bi_idx, su posición dentro del vector, y bi_vcnt, el número de estructuras bio_vec en el vector. Dicho esto sólo queda detallar que las estructuras bio_vec se encargan de almacenar la información relativa a la página de memoria donde se aloja el segmento, su tamaño, y el desplazamiento dentro de la página.
Con esta organización, el subsistema de entrada/salida de dispositivos de bloques de Linux es capaz de describir operaciones, ordenarlas a conveniencia(normalmente por proximidad de páginas dentro del almacenamiento masivo), añadir y eliminar operaciones con relativa facilidad, y utilizar distintos esquemas de acceso al dispositivo de bloques. El panorama general se asemeja a la siguiente figura:
El sistema de ficheros virtual maneja todas estas estructuras, a través de una estructura request_queue, que, como indica su nombre, es una cola de peticiones. Mientras la cola no esté vacía, el driver del dispositivo de bloques irá retirando peticiones, o estructuras request, de la cabeza de la cola y enviándole dichas peticiones al dispositivo que soporta. La estructura request está compuesta de una o más estructuras bio que operan sobre bloques consecutivos en el dispositivo. Estas estructuras vienen definidas en el fichero “include/linux/blkdev.h”.
Estas colas de peticiones serán manejadas por los distintos organizadores de las operaciones de entrada/salida. Entre los organizadores más importantes nos encontramos, el ascensor de Linus(Linus Elevator Scheduler), el organizador por plazos(Deadline Scheduler), el organizador anticipado(Anticipatory Scheduler), el organizador de cola completamente justo(Complete Fair Queue Scheduler), y el organizador de no operación(Noop Scheduler).
3.5.2 Dispositivos de red.
Los dispositivos de red en Linux disponen de un comportamiento similar al de un dispositivo de bloques montado. Éste debe registrarse en el núcleo del sistema operativo utilizando las estructuras habilitadas para tal efecto antes de que pueda transmitir o recibir ningún paquete. Aún así, parece razonable pensar que existen ciertas diferencias. Los dispositivos de red no disponen de un fichero en el directorio “/dev” que sirva de punto de acceso, puesto que sus operaciones principales ya no van a ser las de leer o escribir, sino las de transmitir o recibir. Además, utiliza un espacio de nombres distintos y provee al usuario de operaciones distintas, que operan sobre objetos diferentes en el núcleo.
Otra diferencia a subrayar es que un dispositivo de bloques actúa como consecuencia de una petición del núcleo, mientras que el dispositivo de red debe pedir al núcleo que procese el paquete que ha recibido asíncronamente. Esto implica que aunque el subsistema de red de este sistema operativo sea independiente del protocolo utilizado, su API sea completamente distinta.
La forma en que un dispositivo de red se registra en Linux es insertar una estructura net_device dentro de una lista global de dispositivos de red. Esta estructura se detalla en el fichero “include/linux/netdevice.h”, y engloba toda la información relativa a la comunicación del dispositivo de red, además de información relativa a los diferentes protocolos soportados por el sistema. Los campos más importantes de dicha estructura son el nombre, su estado, la estructura net_device del siguiente dispositivo en la lista de dispositivos de red, los campos relativos a su espacio de memoria relacionado, su dirección, puerto, irq y canal de DMA.
Para alojar dinámicamente la estructura se dispone de la función alloc_netdev, registrando el dispositivo en el núcleo. Sin embargo, también es posible hacer este registro mediante las funciones alloc_etherdev(Ethernet devices, “include/linux/etherdevice.h”), alloc_fcdev(Fiber-channel, “include/linux/fcdevice.h”), alloc_fddidev(FDDI devices, “include/linux/fddidevice.h”) o alloc_trdev(Token ring devices, “include/linux/trdevice.h”). La inicialización de las estructuras es realizada por dichas funciones, colocando unos parámetros por defecto que pueden ser modificados posteriormente. Dicha inicialización la realiza la función ether_setup en el caso de llamar a alloc_etherdev.
En el caso de descargar el módulo de la memoria, debe utilizarse las funciones unregister_netdev y free_netdev para liberar todos los recursos registrados durante la inicialización del modulo.
Durante la apertura y cierre del dispositivo cabe destacar la necesidad de activar y desactivar el bit IFF_UP, del campo flag de la estructura netdevice, para cada correspondiente operación., la necesidad de copiar la dirección MAC del dispositivo a la estructura, y el uso de las funciones net_if_start_queue y net_if_stop_queue, para iniciar o detener las transferencias.
En las transmisiones de paquetes se dispone de la función hard_start_xmit, que encola el envío de un paquete de datos almacenado en la estructura socket buffer(struct sk_buff, detallada en el fichero ”include/linux/sk_buff.h”). Cualquier paquete que se vaya a transmitir por la interfaz de red debe pertenecer a un socket, puesto que el almacenamiento de entrada/salida de esta estructura son listas de estructuras sk_buff. El paquete debe de estar almacenado en la estructura tal y como deba aparecer en la interfaz física de red desde la que se vaya a enviar.
Es necesario que durante las transmisiones se controle el acceso concurrente a la función hard_start_xmit. Para ello se utiliza el mecanismo de spinlock. Si el dispositivo dispone de memoria limitada, debe controlarse además la detención y reanudación de la cola de transmisión. Esto se realiza mediante las funciones netif_stop_queue y netif_wake_queue.
Dichos dispositivos deben contemplar la posibilidad de fallo en las transmisiones a través de temporizadores. Sin embargo, no es necesario que sea el driver quien se encargue de preparar este mecanismo. El driver sólo requiere indicar el periodo de espera del temporizador anotándolo en el campo watchdog_timeo de la estructura net_device. Esta temporización se mide en Jiffies. En caso de expirar este tiempo, se llama al método tx_timeout.
La recepción de paquetes se controla de dos formas, mediante entrada/salida por interrupciones, o, en caso de redes de gran ancho de banda, por entrada/salida programada. Esto último se debe a que en este tipo de redes, la sobrecarga de pasar de contextos de ejecución, a un contexto de interrupción impide que se aproveche adecuadamente el ancho de banda disponible.
Para la recepción controlada por interrupciones, se define una función de recepción que utilice la función dev_alloc_skb para reservar espacio para un paquete en memoria. Esta función será llamada por el manejador de interrupción del dispositivo, contexto de interrupción, para almacenar el paquete en memoria principal, donde será procesado posteriormente.
Es posible liberar el espacio de almacenamiento de un socket buffer mediante las funciones dev_kfree_skb(para contextos de ejecución), dev_kfree_skb_irq(para contextos de nterrupción) y dev_kfree_skb_any(para cualquier contexto).
Para la entrada/salida programada, se utiliza una API diferente llamada NAPI(New API) que modela el comportamiento programado del dispositivo. Un driver que implemente esta API debe ser capaz de deshabilitar la interrupción de recepción de paquetes, manteniendo las interrupciones para las transmisiones válidas y otros eventos. Además, deberá implementar una función poll que se dedique a preguntar a la interfaz de red si ha recibido algo. En caso de no recibir nada, se reactivará la interrupción de recepción y se mantendrá al dispositivo a la espera.
3.5.3 Network Block Device.
Como reza la documentación incluida en el núcleo de Linux, este módulo se encarga de conectar a un servidor remoto que exporte un dispositivo de bloques, con el fin de que la máquina cliente pueda acceder a este como si de un dispositivo local se tratara. Si establecemos una simple comparación con el objetivo de este proyecto, podemos constatar que la única diferencia con este, es que el servidor, no es uno propio, como el nbd-server, sino el servidor Swift de Openstack, luego es razonable pensar que podemos partir de este módulo para alcanzar nuestro objetivo.
Este módulo envía las peticiones de un dispositivo de bloques genérico a través de la red, utilizando el protocolo TCP, para que el servidor la procese y envíe la respuesta al cliente de vuelta de la misma forma. Dichas peticiones, tienen la forma de estructuras request como las utilizadas por los dispositivos locales existentes en el sistema. Estas estructuras request se generan a raíz de las estructuras bio y bio_vec(comentadas en el apartado 5.1.2), tomando la información correspondiente al direccionamiento del bloque, y los bloques consecutivos que se quieren extraer.
El servidor funciona completamente en espacio de usuario, siendo necesario dicho módulo solamente en el cliente.
En el fichero <include/linux/nbd.h> podemos encontrar tres estructuras que definen el dispositivo de bloques, la petición y la respuesta. La estructura del dispositivo es la siguiente:
struct nbd_device {
int flags;
int harderror; /* Code of hard error */
struct socket * sock;
struct file * file; /* If == NULL, device is not ready, yet */
int magic;
spinlock_t queue_lock;
struct list_head queue_head; /* Requests waiting result */
struct request *active_req;
wait_queue_head_t active_wq;
struct list_head waiting_queue; /* Requests to be sent */
wait_queue_head_t waiting_wq;
struct mutex tx_lock;
struct gendisk *disk;
int blksize;
u64 bytesize;
pid_t pid; /* pid of nbd-client, if attached */
int xmit_timeout;
};
Sus campos más destacables son el entero flags para configurar sus parámetros, la estructura socket que realiza el envío y recepción de peticiones, la estructura file que define el fichero del dispositivo y las dos colas de peticiones active_wq y waiting_wq. El driver se apoya en dichas colas para ejercer sus funciones. Una petición nueva, es encolada en la cola waiting_wq. Cuando llega su turno en la cola, la petición se envía y pasa a la cola active_wq para esperar su respuesta. Estas operaciones sobre las listas suceden tras la protección de concurrencia del mecanismo de spinlock. Ademas, con el fin de evitar problemas de concurrencia, se protege la transferencia de estructuras mediante un mutex.
La estructura request de NBD es la siguiente:
struct nbd_request {
__be32 magic;
__be32 type; /* == READ || == WRITE */
char handle[8];
__be64 from;
__be32 len;
} __attribute__((packed));
Esta estructura dispone de un número mágico(o número único) para asegurar la consistencia de la petición, un tipo(lectura o escritura), el campo handle, que se utiliza para almacenar la dirección del manejador de la petición en curso, from, para almacenar la posición dentro del dispositivo donde se encuentra el fichero a tratar, y len, el tamaño de la petición, en múltiplos de bloques.
Finalmente la estructura reply se compone de lo siguiente:
struct nbd_reply {
__be32 magic;
__be32 error; /* 0 = ok, else error */
char handle[8]; /* handle you got from request */
};
Podemos observar que dispone de campos similares a los de la estructura request, exceptuando que, en vez de denotar un tipo de respuesta, se almacena un posible código de error. Tampoco se requiere la posición de los bloques solicitados.
De entre todas las funciones que conforman dicho driver, alojado en <drivers/block/nbd.c> nos interesan mayoritariamente las funciones que se encargan de transmitir y recibir paquetes a través de la red, y las funciones que creen o procesen dichos paquetes pues son las funciones susceptibles de modificación. Entre ellas, encontramos la función sock_xmit, que se encarga de transmitir un buffer utilizando el socket propio del dispositivo. Gracias a que su objetivo es muy simple, esta función no requiere ningún cambio, suponiendo una base de la comunicación imprescindible. Sin embargo, podemos observar las funciones sock_send_bvec y nbd_send_req, que se encargan de utilizar sock_xmit para enviar la estructura request y las estructuras bio_vec requeridas para procesar la operación en curso, y que con toda probabilidad deberán ser modificadas para permitir la comunicación con el servidor Swift. Estas dos funciones componen la comunicación en el sentido cliente-servidor.
Las funciones sock_recv_bvec y nbd_read_stat son el equivalente a las funciones sock_send_bvec y nbd_send_req en el sentido opuesto de la comunicación, es decir, la comunicación servidor-cliente. La primera de ellas recibe las estructuras bio_vec requeridas, y la segunda se encarga de procesar la respuesta. Con el diseño adecuado, dichas funciones no deberían ser susceptibles de cambio.
La función nbd_handle_req añade la petición activa a la cabeza de la cola de peticiones activas, donde esperará su respuesta correspondiente. La función do_nbd_request será la encargada de encolar una nueva petición en la cola waiting_queue antes de ser enviada.
Se definen en este módulo una serie de operaciones ioctl para trabajar con la estructura del dispositivo NBD, las cuales no resultan de especial interés para el cometido de este proyecto.
3.6 Windows
Dentro de la arquitectura del conocido sistema operativo de Microsoft nos encontramos con diversos componentes organizados de la siguiente manera:
En la figura podemos diferenciar los subsistemas que trabajan en espacio de usuario y en espacio del núcleo. Dentro del espacio de usuario tendremos:
Procesos de soporte del sistema: ejemplos de este subsistema son el proceso de identificación, el organizador de sesiones y otros procesos que no son iniciados por el organizador de control de servicios
Servicios del sistema y aplicaciones de usuario: éstos se apoyan en librerías dinámicas del sistema, y los subsistemas de entorno para realizar su cometido.
Las propias librerías dinámicas: su cometido es traducir la llamada al sistema realizada por el servicio, o la aplicación de usuario, a la función adecuada dentro del sistema. Para ello, si lo requiere, la librería enviará un mensaje al subsistema de entorno.
En el espacio del núcleo, los componentes más importantes son:
El ejecutor de Windows, que conforma la base principal del sistema, ofreciendo los servicios mínimos del sistema(uso de memoria, creación de procesos e hilos, seguridad, entrada/salida...)
El núcleo, que reúne las funciones en bajo nivel requeridas para que el ejecutor ofrezca los servicios mínimos
Los drivers de dispositivos, que se comunicarán con el hardware
La capa de abstracción de hardware, que aísla el núcleo del ejecutor de Windows. Como se puede observar, el sistema gráfico es independiente del bloque de sistemas antes comentado.
Analizando en mayor detalle los subsistemas que componen el núcleo, podemos encontrar los siguientes componentes:
El manejador de entrada salida: dicho subsistema se apoya en los drivers de dispositivos o en los sistemas de ficheros para realizar delegar la tarea encomendada al componente correspondiente.
El manejador de cache, que se encarga de organizar los datos requeridos recientemente por el espacio de usuario en memoria, manteniéndolos en esta para agilizar posteriores accesos.
El manejador de procesos e hilos, que se encarga de iniciar y finalizar los distintos procesos e hilos y de ofrecerles el soporte requerido.
El manejador de objetos del núcleo, cuyo objetivo principal es mantener las estructuras necesarias para su correcto funcionamiento.
El manejador de dispositivos Plug and Play, subsistema que detecta la aparición de un nuevo dispositivo en el sistema y pone en funcionamiento las infraestructuras necesarias del núcleo que permitirán su correcto funcionamiento.
El monitor de seguridad, que activará las políticas de seguridad de acceso a cualquier medio.
El manejador de memoria, que se encarga de alojar información en memoria principal, o devolverla al dispositivo de bloques del que proviene.
El manejador de configuración del sistema, que gestionará el registro de Windows y la información contenida en él.
El manejador de entrada/salida, que implementa una interfaz general de entrada salida independiente del dispositivo.
Las rutinas de instrumentación ofrecidas por el servicio WMI de Windows, que permiten a los drivers de los dispositivos publicar información de configuración y estadísticas sobre el dispositivo.
El invocador de procedimientos avanzado, que realiza las tareas necesarias para la ejecución de cualquier software.
La interfaz de dispositivos de gráficos, apoyada en sus correspondientes dispositivos, y que realizará la tarea de gestionar el entorno gráfico del sistema.
Estos subsistemas están gobernados por el ejecutor de Windows y quedan resumidos en la siguiente imagen:
Analizaremos posteriormente en mayor detalle los subsistemas más relevantes.
3.6.1 Subsistema de entorno y librerías dinámicas.
Este componente ofrece aislamiento entre el espacio de usuario y el espacio del núcleo, sin llegar a pertenecer a este último. Actúa como un filtro de primera instancia donde una función de dichas librerías puede actuar de las siguientes formas:
La función se implementa en la librería, y por tanto, el espacio del núcleo no requiere saber de la llamada a esta función, luego será resuelta por la librería y se continuará con la ejecución normal del sistema.
La función requiere llamar al ejecutor de Windows para realizar alguna de sus funciones, luego se realizarán algunas tareas puntuales en espacio del núcleo y se continuará el resto de ejecución en espacio de usuario.
La función requiere el uso del subsistema de entorno, enviándole un mensaje para que resuelva una tarea concreta, y continúe su ejecución posteriormente.
Estos comportamientos se resumen en la siguiente figura:
Entre los subsistemas de entornos, nos encontramos con el subsistema propio de Windows, y, por cuestiones de soporte, el subsistema POSIX (Portable Operating System Interface based on Unix). Puesto que nuestro objetivo se centra en el espacio del núcleo, no estudiaremos en mayor profundidad este componente.
3.6.2 Ejecutor de Windows.
El ejecutor de Windows conforma la primera capa de abstracción con la que una llamada al sistema se encontrará cuando se alcance el espacio del núcleo, haciendo de fachada para el resto del sistema. Está formado por numerosas funciones que pueden ser recogidas en los siguientes tipos:
Funciones exportadas al espacio de usuario, o servicios del sistema, la librería Ntdll se encarga de ofrecerlos. Muchos de los servicios son accesibles desde la API de Windows, o a través del subsistema de entorno.
Drivers de dispositivos, llamados a través de la función DeviceIOControl y que ofrece una interfaz general al espacio de usuario para llamar a los servicios de un dispositivo.
Funciones que sólo pueden ser llamadas desde el espacio del núcleo.
Funciones definidas como símbolos globales, pero que no son exportados al espacio de usuario.
Además, el ejecutor de Windows contiene cuatro grupos de funciones de soporte para los componentes organizados por éste. Los grupos son los siguientes:
Las funciones del manejador de objetos del ejecutor, que crea y destruye los objetos que sirven de infraestructura para el núcleo.
El ALPC(Advanced Local Procedure Call, lanzador de procedimientos locales avanzados) que se encarga del paso de mensajes entre procesos.
La librería de funciones comunes del sistema.
Las rutinas de soporte del ejecutor.
3.6.3 El núcleo.
El núcleo consiste en una serie de funciones contenidas en Ntoskrnl.exe que ofrece los mecanismos principales para el funcionamiento del sistema. Es utilizado por el ejecutor como una arquitectura de hardware a bajo nivel y es completamente dependiente, por tanto, de la arquitectura donde se ejecute.
Dentro de él se utilizan objetos de control y primitivas que serán controladas por el ejecutor, que expondrá los recursos compartidos utilizando las distintas políticas de seguridad. Además dispone del control de región del procesador y del bloque de control para almacenar información específica del procesador y su ejecución en curso.
3.6.4 Drivers de dispositivos.
Los drivers en Windows conforman módulos que pueden cargarse en memoria principal bajo demanda. Pueden ser utilizados en contextos donde un hilo en espacio de usuario utiliza una de sus funciones, en contextos en los que un hilo en espacio del núcleo lo utiliza, o como resultado de una interrupción. Los drivers no utilizan los dispositivos directamente, sino que utilizan las funciones de la capa de abstracción de hardware(HAL, Hardware Abstraction Layer) para cumplir su cometido. Los tipos de drivers existentes son los siguientes:
Drivers de dispositivos, que utilizan la capa HAL para comunicarse con estos.
Drivers de sistemas de ficheros, que aceptan peticiones genéricas de entrada salida y las traducen a peticiones de un dispositivo concreto.
Drivers de filtro de sistemas de ficheros, que colaboran con los de sistemas de ficheros para ofrecerle una nueva funcionalidad.
Redirectores de red y servidores, que, siendo drivers de sistemas de ficheros, transmiten las peticiones de un sistema de ficheros a través de la red y reciben las peticiones.
Drivers de protocolos, los cuales implementan los protocolos de comunicaciones básicos y más extendidos como TCP/IP, NetBEUI e IPX/SPX.
Drivers de filtro para flujos de ejecución del núcleo, que implementan el procesos de señalización en grandes flujos de datos.
Con esto, el modelo de drivers puede ser resumido en tres tipos concretos:
Drivers de bus, que se encargan de utilizar los controladores de bus, adaptadores, puentes, y cualquier dispositivo capaz de comunicar diferentes dispositivos.
Drivers de función, que implementan la funcionalidad principal de un dispositivo en cuestión.
Drivers de filtro, que implementan una funcionalidad añadida a un driver de función.
Para el desarrollo de estos drivers, la Windows Driver Foundation provee al usuario de las plataformas para drivers en espacio del núcleo(KMDF, Kernel-mode driver framework), y para drivers en espacio de usuario(UMDF, User-mode driver framework).
3.6.5 Componentes del sistema de entrada/salida.
Dentro del sistema de entrada/salida existen una serie de componentes que colaboran habitualmente para ofrecer los servicios de los drivers al espacio de usuario. Estos componentes son el gestor de entrada/salida, el gestor del consumo energético, el gestor de dispositivos Plug and Play, y las rutinas de servicio para el proveedor de instrumentación de Windows.
El gestor de entrada salida es el principal componente de este subsistema, cumpliendo el objetivo de conectar dispositivos(virtuales, lógicos y físicos) con el espacio de usuario. Conforma una infraestructura de soporte para los drivers.
Los drivers de dispositivos ofrecen una interfaz de comunicación que es dirigida a través del gestor de entrada/salida. Cuando el usuario envía una petición, el gestor de entrada/salida la recoge y decide quien debe responder a esta, dirigiendo la petición al dispositivo adecuado.
El gestor de dispositivos Plug and Play permanece en contacto continuo con el gestor de entrada/salida y con los drivers de bus. Los drivers de bus le ofrecen alojamiento para los nuevos dispositivos, aparte de avisar de la detección de un nuevo dispositivo, o de su desaparición. Este subsistema cargará los módulos correspondientes y si no dispone de ellos, llamará a un gestor en el espacio de usuario.
El gestor de consumo energético realizará las transiciones adecuadas de los dispositivos entre los distintos estados de consumo(encendido, apagado, modo de bajo consumo, etc).
Las rutinas de servicio del proveedor de instrumentación de Windows hacen de interfaz de comunicación entre el proveedor de instrumentación y los drivers de los dispositivos.
Además de estos componentes, es conveniente recordar que en este subsitema utiliza el registro de Windows para mantener una base de datos de los dispositivos básicos, los ficheros INF, que sirven para almacenar la información de los drivers instalados en el sistema, y el nivel de abstracción de hardware.
3.6.5.1 Gestor de entrada/salida.
Este subsistema sirve de infraestructura para recoger peticiones de entrada/salida, en forma de paquetes llamados IRP(I/O Request Packet). Su diseño permite que un hilo de procesamiento se encargue de múltiples peticiones concurrentemente. El IRP contiene la información necesaria para describir la petición. El gestor de entrada/salida enviará un puntero a la dirección de memoria de la petición al driver correspondiente, donde será procesada. Este mismo puntero se utilizar para recuperar la información de la petición., cuando el driver notifique que la operación ha concluido.
Por otro lado este subsistema ofrece un código común para todos los dispositivos, permitiendo que los drivers reduzcan su tamaño, retirándoles el código de las tareas comunes con otros dispositivos.
Los drivers disponen de una interfaz modular y uniforme, de forma que el gestor de entrada salida puede tratarlos sin necesidad de tener ningún conocimiento acerca de como procesa las peticiones. De esta forma, el driver sólo tiene que encargarse de traducir una petición genérica a una petición especifica del hardware que lo gestiona. Es posible que los drivers se llamen unos a otros para resolver la gestión de la petición en curso.
Una operación típica de entrada/salida, es tratada por el gestor de entrada/salida, uno o dos dispositivos, y el nivel de abstracción de hardware. El sistema operativo, enmascara los dispositivos, de forma que el espacio de usuario dispondrá de las operaciones normales en un fichero cualquiera.
3.6.5.2 Drivers de dispositivos.
Atendiendo a la clasificación de si se trata de un driver en espacio de usuario o en espacio de núcleo, podemos constatar que en el espacio de usuario disponemos de tres tipos: Drivers de Dispositivos Virtuales(VDDs), drivers de impresoras, y drivers de la infraestructura de drivers en espacio de usuario.
Los drivers de dispositivos virtuales se mantienen por compatibilidad con MS-DOS, ya que emulan aplicaciones de 16 bits, capturando sus llamadas a dispositivos y traduciéndolas a los dispositivos reales.
Los drivers de impresoras traducen las peticiones de dispositivos de gráficos genéricos a comandos de impresión.
Los drivers de la infraestructura de drivers en espacio de usuario son drivers de dispositivos que no requieren ejecución en espacio del núcleo, y que, en caso de necesitar algo, les es suficiente con comunicarse a través de las llamadas a procesos locales avanzados de Windows.
En el espacio del núcleo, disponemos de los drivers de sistemas de ficheros, que recogen las peticiones y las envían al dispositivo de bloques, o interfaz de red correspondiente, los drivers de dispositivos Plug and Play(PnP), entre los que se incluyen drivers de dispositivos de almacenamiento masivo, adaptadores de video, dispositivos de en entrada e interfaces de red, y drivers de dispositivos no Plug and Play, que conforman extensiones del núcleo, o nuevas funcionalidades para otros drivers.
Por otro lado, se encuentran los drivers pertenecientes al modelo de drivers de Windows(Windows Driver Model). Entre éstos encontramos los drivers de bus, los drivers de funcionalidad, y los drivers de filtro comentados anteriormente.
Puesto que un driver puede ser dividido en distintos niveles, es posible clasificarlos en otros tres tipos distintos, de clase, de puertos o de minipuertos.
Los dispositivos de clase implementan drivers para una clase en particular de driver(drivers de discos, de cintas, de CR-ROM...).
Los dispositivos de puertos procesan peticiones para enviarlas por un tipo específico de puerto, como un puerto SCSI, COM, USB. Éstos se implementan como librerías del núcleo, ya que son drivers escritos por Microsoft con el sistema operativo.
Los drivers de minipuertos traducen una petición genérica a un petición de un puerto concreto, siendo drivers para un dispositivo que importan las funciones de un dispositivo de puerto.
Si una petición debiera ser atendida por un driver con diferentes niveles, el gestor de entrada/salida recogería la petición del espacio de usuario, la traduciría a una petición para el driver que conforme el primer nivel, con su respuesta, generaría una nueva petición para el siguiente nivel, y seguiría generando posteriores peticiones sucesivamente hasta llegar a la última. Una vez respondida la última petición, se devolvería la respuesta al espacio de usuario, conformando una cola de peticiones por nivel.
3.6.5.3 Estructura de un driver.
Dentro de un driver se encuentran una serie de funciones que resuelven las diversas tareas que requiere el sistema operativo para ofrecer el resultado de las operaciones al espacio de usuario. Pueden dividirse en dos subgrupos, las funciones principales, que se encuentran en todo driver, y las funciones auxiliares, que según las infraestructuras que requiera el driver, aparecerán, o no lo harán.
Dentro de las rutinas principales, o imprescindibles tenemos las rutinas de inicialización del driver(DriverEntry) que se utilizan recién cargado el driver en memoria principal, e inicializa las estructuras principales del driver.
Para el buen funcionamiento de la infraestructura PnP se incluye una rutina de añadido de dispositivo(add-device routine) que recibe la notificación del gestor PnP y aloja el objeto del dispositivo descubierto en el núcleo.
Es posible encontrar una serie de rutinas dentro del driver que conforman las operaciones que puede realizar el usuario sobre el dispositivo. Las principales suelen ser las rutinas de apertura, cierre, lectura y escritura, aunque pueden existir otras en función de las operaciones que ofrezca el dispositivo.
Si el driver se sirve de las colas de gestión proveídas por gestor de entrada/salida, definirá una operación start que iniciará la transferencia de peticiones. El gestor de entrada/salida se encargará entonces de serializar las peticiones y enviarlas una a una al dispositivo.
Por último se definen dos funciones relacionadas con el uso de interrupciones, la rutina de servicio de interrupción(ISR), y la rutina de resolución para el servicio de interrupción. La primera completará las labores mínimas de procesado, pues su ejecución debe ser lo más rápida posible. Habitualmente encola la petición, para que sea procesado posteriormente en la rutina de proceso del servicio de interrupción.
El resto de rutinas que detallamos a continuación, pertenecen al segundo grupo antes comentado, es decir, estas rutinas aparecen en función de ciertas condiciones que debe cumplir el driver, y de no cumplirse no existe la necesidad de implementarla.
Si el driver dispone de diferentes niveles encontraremos en este al menos una, aunque pueden ser varias, rutinas de finalización. Estas rutinas avisan a los niveles superiores de la resolución de la operación en niveles inferiores, y del estado de esta(correcta, incorrecta, cancelada).
El driver puede ofrecer una o varias rutinas de cancelación. Si la petición puede ser cancelada, esta incluirá la rutina que debe ejecutarse en caso de recibir una petición de cancelación. Esta rutina realizará labores de limpieza para que el driver pase a un estado consistente para procesar nuevas peticiones.
Si el driver está preparado para comunicarse con el gestor de cache, debe implementar una rutina de resolución rápida. Esta rutina permitirá resolver la petición a través de la cache del sistema, evitando realizar nuevas operaciones de entrada salida.
Es posible, pero no necesario, que el driver disponga de una rutina de descarga, para realizar las labores de liberación de memoria y recursos que este utilizara durante su ejecución. Además, es posible definir una rutina, aparte, para la limpieza de las infraestructuras en el apagado del sistema.
Por último, puede definirse una rutina de registro de errores.
3.6.5.4 Objetos de drivers y objetos de dispositivos.
El gestor de entrada/salida se comunica con dos tipos de objetos que le ofrecen las operaciones disponibles y cualquier información que necesite saber para llevarla a cabo. Son los objetos de driver, y de dispositivo. Los objetos de drivers representan el driver que puede manejar los distintos dispositivos conectados al sistema, y los de dispositivo, los dispositivos, físicos o lógicos que pueden ser gobernados por dicho driver.
Cuando el driver es cargado en memoria, el objeto de driver es creado. Inmediatamente después se llamará a la rutina de inicialización. A partir de entonces es posible crear objetos de dispositivo gobernados por dicho driver. Para ello se utilizan las operaciones IoCreateDevice e IoCreateDeviceSecure, aunque habitualmente los dispositivos utilizarán la rutina add-device y la infraestructura Plug and Play.
Al crearse el dispositivo, es posible asignarle un nombre que será almacenado en el gestor del espacio de nombres de objetos, que es inaccesible directamente desde el espacio de usuario. Para que sea accesible, debe hacerse un enlace desde el directorio \Global hacia el directorio \Device, del espacio de nombres, donde el nombre del objeto realmente se encuentra. Para exportar las interfaces se utiliza la función IoRegisterDeviceInterface, y para habilitarlas, se utiliza IoSetDeviceInterfaceState. Desde ese momento, el usuario puede utilizar la infraestructura PnP para utilizar la interfaz.
Con esta infraestructura, un objeto driver es asociado con los distintos objetos dispositivos. De esta forma, el gestor de entrada/salida no requiere conocer los detalles de un dispositivo concreto.
3.6.5.5 Estructuras de ficheros.
Las estructuras de ficheros cumplen los requisitos de diseño de los objetos en Windows(son recursos compartidos, pueden tener nombre, deben estar protegidos y soportan sincronización). Su única diferencia, es que los objetos compartidos suelen estar alojados en memoria, y en los ficheros, se encuentran en un dispositivo físico. Su representación se compone de los siguientes atributos:
Nombre de fichero.
Desplazamiento del byte actual(respecto al comienzo del fichero).
Modo de compartición.
Banderas de modo de apertura.
Puntero al objeto de dispositivo.
Puntero a la partición.
Puntero a la sección de punteros de objetos.
Puntero al mapa privado de cache.
Lista de paquetes de petición de entrada/salida.
Contexto de finalización de entrada/salida: de existir, comunica el estado de finalización de la petición en curso sobre el puerto
Extensiones del objeto de fichero: almacena la prioridad, si requiere comprobaciones de seguridad, y si contiene extensiones que almacenan información de contexto.
El nombre y el desplazamiento se utilizarán para posicionarse en la lectura secuencial del fichero. El modo de compartición y las banderas de modo de apertura controlan la seguridad de los accesos. El puntero al dispositivo, a la partición, y a la sección de punteros de objetos identifican el dispositivo físico que aloja el fichero, el mapa de cache y la lista de IRPs para procesar las peticiones. Las extensiones del objeto de fichero pueden ser las siguientes:
Parámetros de transacción.
Detalles del objeto de dispositivo: Identifica el driver de filtro adecuado.
Estatus de bloqueo de rango de entrada/salida: permite al espacio de usuario bloquear buffers en el espacio del núcleo optimizando las operaciones asíncronas.
Genérica: almacena información específica para el driver de filtro.
Entrada/salida programada del fichero: almacena la reserva de ancho de banda del dispositivo para garantizar el buen funcionamiento de aplicaciones multimedia.
Enlaces simbólicos: información para resolver enlaces simbólicos.
Cuando el espacio de usuario pide al núcleo la apertura de un fichero, el gestor de entrada/salida se comunica con el subsistema adecuado(en este caso, el gestor de objetos), para que éste, con ayuda del núcleo genere un manejador para el fichero. El gestor de entrada/salida, enviará posteriormente este manejador a la aplicación que la solicitó. Durante este proceso, cualquier comprobación de seguridad debe ser resuelta por el gestor de entrada/salida.
El objeto de fichero es alojado en memoria y es una representación de un recurso compartido, no el recurso en sí. Esta representación almacena los datos necesarios para utilizar el fichero, mientras que el fichero almacena los datos a utilizar. Por otro lado, el manejador debe ser único para un proceso, mientras que el fichero no tiene por que serlo(por ejemplo varios procesos utilizan el mismo fichero). En caso de escrituras, se evitan los problemas de concurrencia utilizando la función de Windows LockFile.
Cuando el fichero se abre, se incluye en el espacio de nombres bajo el directorio “\Device\HarddiskVolumeN”, que representa el directorio de la partición abierta, de la que se recibe el fichero. Para que sea compartido directamente con el espacio de usuario, es necesario hacer un enlace simbólico en el directorio “\Global”.
3.6.5.6 Paquetes de peticiones de entrada/salida.
Las IRPs son las estructuras que Windows utiliza para almacenar la información requerida para realizar una operación sobre un dispositivo. Si un hilo utiliza un servicio de entrada/salida, el gestor de entrada salida generará el IRP correspondiente a la operación solicitada. El sistema gestiona las peticiones utilizando dos colas y una pila, una cola corta para las operaciones que sólo utilicen una posición en la pila, y una cola larga para las que utilicen múltiples posiciones en la pila. Para mejorar la gestión de operaciones entre varios procesadores, se añade una lista global además de las anteriores.
Por defecto un IRP utiliza 10 posiciones en la pila de IRPs, y, por tanto, se aloja en la lista larga de IRPs. Una vez por minuto, el sistema ajusta cuantas posiciones de la pila requiere, llegando a permitir un máximo de 20 posiciones. Después de su alojamiento e inicialización, el gestor de entrada/salida almacena un puntero al solicitante del servicio en el IRP.
Dentro de un IRP se diferencian dos partes: una cabecera fija y una o varias posiciones en la pila. La información del tipo de operación se almacena en la cabecera, mientras que en las posiciones de la pila se almacena código de una función (llamada función mayor), sus parámetros, y el solicitante del objeto fichero. La función mayor indica qué rutina de resolución del driver debe ser llamada por el gestor de entrada/salida cuando el paquete se envíe al driver. Opcionalmente, puede almacenarse una función(llamada menor) para que modifique el comportamiento de la función mayor (añadiendo funcionalidades, comunicando con otros subsistemas, etc).
Habitualmente se ofrecen rutinas de resolución para las operaciones más habituales (apertura, cierre, lectura, escritura, control de entrada/salida, Plug and Play y alguna rutina para WMI).
Una vez almacenado el IRP en la lista correspondiente, el sistema es capaz de encontrarlo y cancelarlo en caso de descontrol.
La creación de un IRP se realiza utilizando los servicios NtReadFile, NtWriteFile, o NtDeviceIoControlFile. El gestor de entrada/salida decide si debe participar en la gestión de los buffers de almacenamiento requeridos por la operación. Puede actuar de tres formas. La primera de ellas sería almacenar un buffer del mismo tamaño que el que ofrece la aplicación que solicita el servicio. En caso de escritura, copia el buffer a su almacenamiento privado, y en el caso de lectura copia la información de su buffer al espacio de usuario.
Otra opción es realizar entrada/salida directa, bloqueando el buffer en espacio de usuario para que no pueda ser escrito, y desbloqueándolo cuando se finalice la operación.
Por último el gestor puede sencillamente no ejercer ninguna gestión de almacenamiento. En este caso, debe ser el driver quien se encargue de estas labores.
En cualquier caso, el gestor de entrada/salida inicializará las referencias adecuadas para localizar el almacenamiento. El tipo de gestión utilizado es seleccionado en función de lo que solicite el driver, el cual registra el tipo deseado para la lectura y escritura, mientras que para el control se almacena en un código de control específico.
Usualmente, para transferencias menores que una página de memoria(4KB) se utiliza la entrada/salida a través de buffers, y para transferencias mayores la entrada/salida directa. Es poco común utilizar la gestión de almacenamiento desde el driver por la posibilidad de que se pierda la referencia al solicitante. Su uso se reduce entonces a drivers en espacio de usuario que garantizan que las referencias son válidas, y pertenecientes al espacio de usuario
3.6.5.7 Petición en drivers de una sola capa.
Este proceso conlleva siete pasos:
Envío de la petición a través del subsistema de DLLs.
El subsistema de DLLs solicita el servicio NtWriteFile.
El gestor de entrada/salida crea el IRP correspondiente y lo envía al driver haciendo uso de IoCallDriver.
El driver transmite el IRP al dispositivo y comienza la operación de entrada/salida.
El dispositivo notifica la finalización del servicio mediante una interrupción.
El driver sirve la interrupción.