설정된 로케일이 영문으로 설정된 경우는 문제없겠지만, 아래와 같이 한글로 설정된 경우에는 위 스크립트는 정상적으로 동작이 안될 것이다.
sunny@sunny-desk:~/project/01_WBS/0.2$ svn info
경로: .
URL: https://svnhost/svn/ProjectRepo/trunk
저장소 루트: https://svnhost/svn/ProjectRepo
저장소 UUID: 0af9682f-c0b8-1742-9153-f717f418bd9a 리비전: 13
노드 종류: 디렉토리
스케쥴: 일반
마지막 수정 작업자: sunny
마지막 수정 리비전: 13
마지막 수정 일자: 2012-01-17 20:58:14 +0900 (2012-01-17, 화)
따라서, 시스템 로케일이 한글이 경우에도 쉘스크립트가 제대로 동작되게 하려면, 아래와 같이 export LC_ALL=C을 추가하면 된다.
Each screen can have one or more layers. Usually, a screen will have
only one, but system that support overlays may have multiple layers
with the hardware letting one layer show through another.
A screen cannot directly create a window, but you can ask a screen
for the ID of its primary layer, then ask the IDirectFB interface to
give you an interface to that layer, then use that interface to create
a window.
Each window has a surface associated with it; drawing to that
surface isn't immediately visible on the screen, as the window manager
is responsible for compositing the surfaces of the windows to the
primary surface based on their update regions and the stacking order,
and the window manager is notified of changes by using the Flip()
method of the surface.
A layer also has a surface associated with it; this surface is a
direct representation of the layer's screen memory. You can only access
this surface when you're in exclusive mode, otherwise you need to
create a window and have the window manager draw to the surface.
The problem starts in the memory chip. Memory is capable to read
certain number of bytes at a time. If you try to read from unaligned
memory address, your request will cause RAM chip to do two read
operations. For instance, assuming RAM works with units of 8 bytes,
trying to read 8 bytes from relative offset of 5 will cause RAM chips
to do two read operations. First will read bytes 0-7. Second will read
bytes 8-15. As a result, the relatively slow memory access will become
even slower.
Lightweight web servers are Web servers
which have been designed to run with very small resource overhead
because of hardware, environment, or simply for the challenge of it.
Many of these systems have been created as a mental exercise to
determine if a modern webserver could be written to run on limited
resources such as those provided in a graphing calculator, a Commodore 64, or in 64 kB (64 KiB) total of memory. Others have been written as commercial endeavors to create webservers with low overhead for embedded systems (network router configuration pages) or low memory environments.
objcopy는 오브젝트 파일을 다른 오브젝트 파일로 복사할 때 사용한는데, 선택적으로 필요한 부분만 을 복사할 수 있기 때문에 파일의 사이즈를 줄이는 데 주로 사용되고, 바이너리 포맷을 바꾸는 데도 이 용된다. ex) objcopy hello hello.new hello 파일을 hello.new로 복사, 기존의 hello와 동일하다. ex) objcopy -O binary hello hello.new 위 명령은 hello 파일에서 인스트럭션과 데이터만을 뽑아서 hello.new로 만든다. 이렇게 만들어진 hello.new는 ELF 헤더도 붙지 않은 순수한 바이너리 그 자체다. 주로 부트 로더를 만들 때 이런 식으 로 사용하는데 이유는 모든 CPU마다 전원이 인가되었을 때 CPU가 인스트럭션을 처음 읽어서 실행할 번지가 정해져 있다. 예를 들면 i386 계열은 0xfffffff0 번지이고 ARM 계열 CPU는 0x0 번지며 MPC860 CPU 같은 경우 0xff00100번지다. 무슨 말이냐하면 i386 계열 CPU는 전원이 켜지는 순간 BIOS의 POST(Power On Self Test) 프로그 램이 위치하는 0xfffffff0 번지의 인스트럭션을 제일 처음 읽어서 수행한다. i386 PC 외의 CPU 같은 경우 처음 수행하는 프로그램은 부트 로더인데 이러한 부트 로더는 인스트럭 션이 제일 앞에 와있는 바이너리여야 한다. 즉 ELF 헤더가 붙어서는 안 된다는 이야기다. ELF 헤더는 운영체제에서 필요한 정보지 CPU에서 필요한 정보는 아니다. ex) objcopy -S hello hello.new 모든 심볼들과 재비치 정보들을 제거하여 hello.new 파일을 만든다. 그래서 바이너리 파일의 사이즈가 확실히 줄어든다. - 참고 문헌 : 유닉스, 리눅스 프로그래밍 필수 유틸리디
보통 커널의 파라미터를 조정할때 proc 에서 echo 를 이용하여 redirect
로 직접 쓰는 경우가 많았다. 이렇게 조정을 할 경우에는 또 rc.local 같
은 파일에 따로 기입을 해 줘야 하는 불편함이 있었다.
RedHat 6.2 이후 배포판에는 sysctl 이라는 package가 추가되어 이것들을
관리를 할수 있게 되었다. 일단 조정할수 있는 모든 parameter 들은
$ sysctl -a
명령으로 확인을 할수가 있다. 그리고 특정값을 수정하기 위해서는
/etc/sysctl.conf 에 해당 키(이건 sysctl -a 명령에서 리스트를 확인
할수 있다)와 키값을 지정한 다음
$ sysctl -p
명령으로 바로 적용을 시킬수 있다. 물론 sysctl.conf 에 기입이 되면
부팅시 마다 자동으로 적용이 된다.
만약 잠시만 바꾸어 보고 싶다면
$ sysctl -w net.ipv4.icmp_echo_ignore_all=0
과 같이 직접 값을 넣어 줄수도 있다. 이럴 경우에는 부팅이 되어 있는
순간만 적용이 된다. -p 옵션은 sysctl.conf 가 아닌 다른 파일을 설정
파일로 지정을 할수 있게 한다. 옵션값이 없으면 default 로
/etc/sysctl.conf 를 읽어 들이며 따로 path 를 지정하면 해당 path 에
있는 파일을 읽어 들인다.
ofs는 loff_t 타입인데, loff_t 타입은 long long 타입의 typedef 이다.
따라서, ofs 값을 표시할 때, 위와 같이 %L (L은 long long type) 을 사용하지 않고, %l 이나, %x 등을 사용하면,
ofs 값이 제대로 출력되지 않을 뿐 아니라, 뒤에 출력하는 값마저 영향을 받아서 엉뚱한 값이 출력된다.
1 binutils
별로 어려운게 없다. 그냥 설치할 위치를 지정해 주고 target에다가 mipsel-linux 혹은 mips-linux를 적용해 주면 된다. libdir은 뭔지 나도 잘 모르겠다.
$ cd $(WORK)
$ tar xjf binutils-??.bz2
$ mkdir target; cd target; mkdir binutils
$ cd binutils
$ $(WORK)/binutils-??/configure --prefix=/opt/host/mispel-linux
--target=mipsel-linux --libdir='${exec_prefix}'/mipsel-linux/i386-linux/lib
$ make
$ sudo make install
2. static gcc #
완전한 gcc를 만들기 위해서는 glibc가 필요하지만 아직 glibc를 빌드 하기 전이다. 따라서 static버전의 c언어 컴파일러부터 제작한후에 glibc를 빌드하고 다시 gcc를 빌드하는 과정을 거친다. 이 과정에서 static gcc를 빌드 하기 위해서는 커널 헤더 파일과 시스템 헤더 파일이 필요하다. 다른 곳에서 만들어진 크로스 컴파일러의 것을 복사하거나 단순히 호스트의 /usr/include를 임시로 복사해서 사용할 수도 있다. 단 이때에도 커널 헤더파일은 해당 타겟 시스템 것을 복사하는 것이 좋다. 물론 이 방법들은 모두 에러 가능성을 다분히 지니고 있는 단점이 있다. 이 예제에서는 시스템 헤더 파일(/usr/include)을 작업 디렉토리에 복사하고 asm, asm-generic, linux 디렉토리를 커널 것으로 대체했다.
* 저도 이게 어느 수준의 헤더가 필요한지 잘 몰라서요. 보통은 비슷한 버전 대의 기존 도구의 헤더를 슬쩍 빌립니다. ^_^
binutils을 인스톨 한 후에 그 PATH의 바이너리들을 PATH에 걸어둔다. configure시에 enable-language 는 c만 지정하고 disable_shared옵션을 걸고 앞서 설명한 헤더 파일이 존재하는 디렉토리를 with-headers로 지정한다.
$ export PATH=/opt/host/mipsel-linux/bin:$PATH
$ cd $(WORK)
$ tar xjf gcc-??.bz2
$ cd target; mkdir gcc
$ cd gcc
$ sudo $(WORK)/gcc-??/configure --prefix=/opt/host/mipsel-linux
--target=mipsel-linux --enable-languages=c --disable-shared
--with-headers=/home/romntica/CCK/test/include
$ make
$ sudo make install
configure시에 sudo를 하는 것은 지정한 헤더파일($with-headers)과 함께 필요한 파일들을 $prefix/mipsel-linux/sys-include로 복사 하기 때문이다.
3 glibc #
자 이제 말도 많고 탈도 많은 glibc를 만들어보자. 많은 사람들이 이 부분에서 어려움을 호소하거나 포기하는 경우가 있다. 그렇지만 어려울 것 없다. 검색 엔진을 활용하면 된다.
google에서 mips glibc로 검색을 해보았더니 관련 패치에 관한 사이트가 몇 개 나왔다. 내용들을 잘 살펴보고 필요한 것을 적용하기만 하면 된다. 이 예제에서는 다음의 패치를 적용하였다. 물론 여기서 사용된 패치가 꼭 필요하거나 전혀 필요치 않다거나의 여부는 따지지 않는다. 꼭 이것뿐 만이 아니고 개발하려는 장치명으로 검색하시면 gcc나 binutils나 glibc에도 적용할 만한 유용한 패치가 많이 있습니다. 예를 들어 제가 도시바 TX49계열의 칩을 사용했는데 그냥 하면 MULTX인가?? 곱셈연산자를 활용을 못하는 것을 binutils를 패치하고나면 해당 연산자를 사용할 수 있게 해주는 패치가 있었습니다.
우선 linuxthread를 add-on하기 위해 glibc-?? 디렉토리의 서브디렉토리로 압축 해제해준다.
$ cd $(WORK)
$ tar xjf glibc-??.bz2
$ cd glibc-??
$ cd $(WORK)/target; mkdir glibc
$ cd glibc
$ tar xjf ../glibc-linuxthread-??.bz2
그리고 다음의 패치를 적용한다. (죄송합니다. 컴파일 전에 diff를 때렸어야 했는데 clean해도 깨끗이 안되는군요. 파일이 너무 커지다보니 실수로 지워버린 것도 있을 것입니다. 문의하시면 답변해 드리겠습니다.)
diff -urN glibc-2.3.2/Makeconfig glibc-2.3.2-fixed/Makeconfig
--- glibc-2.3.2/Makeconfig 2003-01-06 05:31:36.000000000 +0000
+++ glibc-2.3.2-fixed/Makeconfig 2004-06-04 16:39:10.711288024 +0000
@@ -637,7 +637,7 @@
$(foreach lib,$(libof-$(basename $(@F)))
$(libof-$(<F)) $(libof-$(@F)),$(CPPFLAGS-$(lib)))
$(CPPFLAGS-$(<F)) $(CPPFLAGS-$(@F)) $(CPPFLAGS-$(basename $(@F)))
-override CFLAGS = -std=gnu99
+override CFLAGS = -std=gnu9x
$(filter-out %frame-pointer,$(+cflags)) $(sysdep-CFLAGS)
$(CFLAGS-$(suffix $@)) $(CFLAGS-$(<F)) $(CFLAGS-$(@F))
패치가 끝났으면 이제 glibc를 만들어 보자. 물론 한번에 될 거라곤 생각 않는다. 나도 이 예제를 무려 세 번이나 다시 빌드해서 성공했다. gcc와 glibc의 버전에 따른 문제에 기인했던 것 같다. 문제가 발생하면 찾아보고 모르겠으면 다시 또 인터넷을 뒤져보자! 아 주의할 사항은 prefix를 /usr로 줘야 한다는 것이다. glibc가 /usr인 prefix는 특별하게 다룬다고 한다.
$ CFLAGS="-O2 -g -finline-limit=10000 -fno-unit-at-a-time" ../../glibc-??/configure --prefix=/usr --build=i686-linux --host=mipsel-linux --enable-add-ons
$ make
$ make install install_root=/opt/host/mipsel-linux/glibc
이렇게 하면 glibc에 인스톨이 된다.
glibc/lib/* 과 glibc/usr/lib/*을 /opt/host/mipsel-linux/mipsel-linux/lib에 복사한다. 또한 glibc/usr/include/*를 /opt/host/mipsel-linux/mipsel-linux/include로 복사한다. 나머지 glibc의 기타 등등은 옵션으로 각각에 맞게 /opt/host/mipsel-linux/mipsel-linux에다가 복사해 넣으면 된다. 이때 복사를 하면서 lib/*이하에 소프트링크가 깨진 것들이 있는데 걍 놔둬두 무방한것도 있고 필요에 따라 수정해 주면 된다. glibc이하는 삭제해도 좋다.
4 gcc 다시 만들기
이제 완전한 버전의 GCC를 다시 만들기만 하면 된다. 해당 작업디렉토리로 가서 깔끔하게 이전에 static으로 만들었던 것을 싹 지우고 다시 만든다.
$ cd $(WORK)/target/gcc
$ rm -rf *
$ $(WORK)/gcc-??/configure --prefix=/opt/host/mipsel-linux
--target=mipsel-linux --enable-languages=c,c++
$ make
$ sudo make install
자 이제 모든 것이 끝났다.
그러나 본인이 여러분에게 권하는 것은 이미 만들어져 널리 쓰이는 툴을 이용하라는 겁니다.
이런짓은 반드시 꼬옥~ 해야 할때만 해야 하는 것이고 나처럼 널널한 사람만 재미로 해보는 것일 뿐입니다.
DDK가 온다면 DDK것을 그대로 이용하시면 되고 그렇지 않더라도 가장 비슷한 것을 찾아서 이용하면 됩니다. 이런 것 만들줄 몰라도 남이 만든걸 이용해서 더 멋진 것을 만든다면 그걸로도 굉장히 큰 행운이라고 생각합니다.
이 문서는 커널의 압축 해제 루틴 이후부터의 커널 패치를
하는 과정에 대하여 시간순으로 기술한 문서이다.
2. 문서
이 문서를 작성하기 위하여 권수호씨가 작성한 문서를 참고 하였다.
http://kelp.or.kr/korweblog/upload/12/20010725160834/045-LinuxKernel-
chapter38_SA1100_Booting_head_S.doc
3. 패치 과정
head.S 의 함수를 추적하기 위한 방법은
돈이 있다면 가장 좋은 것은 에뮬레이터를 이용하는 것이다.
하지만 돈이 없다면...
결국 몸으로 때우는 수밖에 없다...
여기서는 몸으로 때우는 방법을 검토해 보자.
보통 루틴 추적 방법은 확인하고자 하는 위치에 도달했는가 하는 것과
도달 했다면 검사하고자 하는 값을 보아야 하는데..
일단 위치잡는 최악의 방법은 LED를 점멸 시키는 방버이다.
하지만 이것의 문제는 값을 보기가 힘들다는 점
두번째는 시리얼을 이용하는 방법이다.
일단 부팅 메세지가 나오므로
우리는 시리얼을 이용하는 방법을 찾아 보자.
이 시리얼 메세지를 보는 방법은
head.S 를 추적할 때 쓰는 나의 방법이다.
물론 이것은 원칙적으로 arch_decomp_setup() 함수가 호출된 이후에
동작되지만 EZ-M28 에서는 특별히 하는일이 없으므로 그냥 쓴다.
그리고 어셈블러가 조금 약한 나는 그냥
misc.에 출력 함수를 쓰고
이를 어셈블러에서 호출하는 방식을 쓴다.
여기서 알아야 할것은
arm 에서 한두개의 파라메터를 전달하고자 한다면
r0,r1,r2 이 정도만 사용하면 된다.
우리는 이에 대한 루틴을 만들어 보자..
우선 misc.c 에 다음 함수를 추가 한다.
이 함수는 나중에 지우는 것이 좋다. (^^)
void CheckMsg2( int a )
{
char buff[2];
a = a & 0xF;
if( a >= 10 ) buff[0] = a - 10 + 'A' ;
else buff[0] = a + '0' ;
buff[1] = 0;
puts( buff );
}
void CheckMsg( int a )
{
puts("Check1[");
CheckMsg2( a >> 28 );
CheckMsg2( a >> 24 );
CheckMsg2( a >> 20 );
CheckMsg2( a >> 16 );
CheckMsg2( a >> 12 );
CheckMsg2( a >> 8 );
CheckMsg2( a >> 4 );
CheckMsg2( a );
puts("]n");
while(1); // 무한 루프를 돈다.
}
어셈블러에서 호출할대는
bl CheckMsg
라고 하면 되고
r0 에 파라메터 a에 해당하는 값을 넣으면 된다.
체크 포인트를 찍는 방법중에 또 다른 하나는 강제 리셋이다.
어셈블러에서 다음 코드가 강제로 리셋 시키는 것이다.
ldr r1, =0x10000010
mov r0, #1
str r0,[r1]
그리고 가장 일반적인 방법은 붙어 있는 led에 빛나게 하는 것이다.
아래 코드는 EZ-M28 일 경우이다.
ldr r1, =0x1010001c
ldr r0, =0x7 <== 키고 싶은 LED 상태
dled0: str r0,[r1]
b dled0
커널의 압축 해제를 수행하는 함수는
arch/arm/boot/compressed/head.S 화일의
bl decompress_kernel 를 호출하는 부분이다.
이렇게 호출하는 부분은 두가지가 있는데
EZ-M28에서 호출되는 위치는
243 라인이다.
압축이 해제된 이후에
커널의 압축이 풀린 후 풀린 압축 데이타와 실제 커널의 위치가 다르므로
이를 맞추는 작업을 수행한다.
즉 arch/arm/tools/mach-types 에 적혀 있던 대로 182 란 값이
나온다.
이제 정말 실제 커널로 점프했는지를 조사해야 한다.
그래야 압축이 정확하게 풀린 것이니까...
클클
진짜(?) 커널의 시작 위치는
arch/arm/kernel/head-armv.S 이다.
그리고 가장 처음 시작되는 위치는 다음이다.
==================================================================================
/*
* Kernel startup entry point.
*
* The rules are:
* r0 - should be 0
* r1 - unique architecture number
* MMU - off
* I-cache - on or off
* D-cache - off
*
* See linux/arch/arm/tools/mach-types for the complete list of numbers
* for r1.
*/
.section ".text.init",#alloc,#execinstr
.type stext, #function
ENTRY(stext)
mov r12, r0
The previous section described how to load the Linux kernel image
over ethernet using TFTP. This is especially well suited for your
development and test environment, when the kernel image is still
undergoing frequent changes, for instance because you are modifying
kernel code or configuration.
Later in your development cycle you will work on application code or
device drivers, which can be loaded dynamically as modules. If the
Linux kernel remains the same then you can save the time needed for
the TFTP download and put the kernel image into the flash memory of
your INCA-IP board.
The U-Boot command flinfo can be used to display
information about the available on-board flash on your system:
From this output you can see the total amount of flash memory, and
how it is divided in blocks (Erase Units or Sectors). The RO markers
show blocks of flash memory that are write protected (by software) -
this is the area where U-Boot is stored. The remaining flash memory
is available for other use.
For instance, we can store the Linux kernel image in flash starting
at the start address of the next free flash sector. Before we can do
this we must make sure that the flash memory in that region is empty
- a Linux kernel image is typically around 600...700 kB, so to be on
the safe side we dedicate the whole area from 0xB0040000 to
0xB00FFFFF for the kernel image. Keep in mind that with
flash memory only whole erase units can be cleared.
After having deleted the target flash area, you can download
the Linux image and write it to flash. Below is a transcript of
the complete operation with a final iminfo command to check
the newly placed Linux kernel image in the flash ram.
INCA-IP # era B0040000 B00FFFFF
Erase Flash from 0xb0040000 to 0xb00fffff
...... done
Erased 12 sectors
INCA-IP # tftp 100000 /tftpboot/INCA/uImage
TFTP from server 192.168.3.1; our IP address is 192.168.3.65
Filename '/tftpboot/INCA/uImage'.
Load address: 0x100000
Loading: #################################################################
#################################################################
############
done
Bytes transferred = 726443 (b15ab hex)
INCA-IP # cp.b 100000 $(kernel_addr) $(filesize)
Copy to Flash... done
INCA-IP # iminfo $(kernel_addr)
## Checking Image at b0040000 ...
Image Name: MIPS Linux-2.4.20
Created: 2003-07-25 20:13:55 UTC
Image Type: MIPS Linux Kernel Image (gzip compressed)
Data Size: 726379 Bytes = 709.4 kB
Load Address: 80002000
Entry Point: 80196398
Verifying Checksum ... OK
INCA-IP #
Note how the filesize variable (which gets set by
the TFTP transfer) is used to automatically adjust for the actual
image size.
Now we can boot directly from flash. All we need to do is passing the
address of the image in flash with the bootm
command; we also make the definition of the bootargs variable permanent now:
To test booting from flash you can now reset the board (either by
power-cycling it, or using the U-Boot command reset), or you can manually call the boot command which will run the commands in the bootcmd variable:
INCA-IP # run bootcmd
## Booting image at b0040000 ...
Image Name: MIPS Linux-2.4.20
Created: 2003-07-25 20:13:55 UTC
Image Type: MIPS Linux Kernel Image (gzip compressed)
Data Size: 726379 Bytes = 709.4 kB
Load Address: 80002000
Entry Point: 80196398
Verifying Checksum ... OK
Uncompressing Kernel Image ... OK
## Loading Ramdisk Image at b0100000 ...
Image Name: Simple Embedded Linux Framework
Created: 2003-04-08 0:22:03 UTC
Image Type: MIPS Linux RAMDisk Image (gzip compressed)
Data Size: 1370528 Bytes = 1.3 MB
Load Address: 00000000
Entry Point: 00000000
Verifying Checksum ...
This is a general question about relocation of data and bss section.
When code is linked with linker script allocating all sections including
.bss and .data located in flash area
and all the sections are copied by itself into RAM, how are the data and bss
section relocated?
In the case of text, I know, PIC can run text in different memory area from
that assigned by linker.
However, what happens to .bss and .data ?
For example, gloable variable "a" is defined and assigned its address
0xFFF00200 (flash area) by linker
and map file tells its address 0xFFF00200.
When the area in which global variable "a" is located is copied into RAM and
the location of "a" is changed to 0x00000200 (RAM area),
does the code using "a" such as "a = a+1;"operate normally?
If the address of "a" is still 0xFFF00200 after code is copied, "a = a+1"
will not operate normally becausea is located in flash and will not be
updated.
How can the address of "a" be changed to 0x00000200 ?
Kim
"Kim, Jeong-Hwan" wrote:
>
> This is a general question about relocation of data and bss section.
>
> When code is linked with linker script allocating all sections including
> .bss and .data located in flash area
> and all the sections are copied by itself into RAM, how are the data and bss
> section relocated?
> In the case of text, I know, PIC can run text in different memory area from
> that assigned by linker.
> However, what happens to .bss and .data ?
> For example, gloable variable "a" is defined and assigned its address
> 0xFFF00200 (flash area) by linker
> and map file tells its address 0xFFF00200.
> When the area in which global variable "a" is located is copied into RAM and
> the location of "a" is changed to 0x00000200 (RAM area),
> does the code using "a" such as "a = a+1;"operate normally?
> If the address of "a" is still 0xFFF00200 after code is copied, "a = a+1"
> will not operate normally becausea is located in flash and will not be
> updated.
> How can the address of "a" be changed to 0x00000200 ?
>
> Kim
Only initialized data needs to be copied (.data).
I believe that references to .data and .bss variables are
made relative to some register (base address). Change the
contents of the register, and you've relocated your data.
--
CU
Andrew S. andrewsa@com.mmot.com {anti-spam: invert '.m'}
Andrew S wrote:
>
> "Kim, Jeong-Hwan" wrote:
> >
> > This is a general question about relocation of data and bss section.
>
> Only initialized data needs to be copied (.data).
>
> I believe that references to .data and .bss variables are
> made relative to some register (base address). Change the
> contents of the register, and you've relocated your data.
This depends on your compiler and linker, but with Metaware you have
the options of SMALL Data and BSS segments which use R2 and R13.
By default these are not used. You need to specially select that
your data segments are linked as sbss and sdata and SDA_BASE
and SDA2_BASE to make this work.
Read your linker manual. It should tell you how to do all this.
The crudest way to do this is to link your data "in the right place"
and then run an AWK script over your HEX file and change all the
lines with the address in the data area to "just after the
code segment" and then copy the data back where it belongs when
your code starts.
Tom Evans
InitialSurnameAt
tennyson.com.au
Andrew S <andrewsa@com.mmot.com> writes:
>"Kim, Jeong-Hwan" wrote:
>>
>> This is a general question about relocation of data and bss section.
>>
>> When code is linked with linker script allocating all sections including
>> .bss and .data located in flash area
>> and all the sections are copied by itself into RAM, how are the data and bss
>> section relocated?
>> In the case of text, I know, PIC can run text in different memory area from
>> that assigned by linker.
>> However, what happens to .bss and .data ?
>> For example, gloable variable "a" is defined and assigned its address
>> 0xFFF00200 (flash area) by linker
>> and map file tells its address 0xFFF00200.
>> When the area in which global variable "a" is located is copied into RAM and
>> the location of "a" is changed to 0x00000200 (RAM area),
>> does the code using "a" such as "a = a+1;"operate normally?
>> If the address of "a" is still 0xFFF00200 after code is copied, "a = a+1"
>> will not operate normally becausea is located in flash and will not be
>> updated.
>> How can the address of "a" be changed to 0x00000200 ?
>>
>> Kim
>Only initialized data needs to be copied (.data).
>I believe that references to .data and .bss variables are
>made relative to some register (base address). Change the
>contents of the register, and you've relocated your data.
>--
>CU
>Andrew S. andrewsa@com.mmot.com {anti-spam: invert '.m'}
As Andrew pointed out, .bss is simply zeroed so, as long as your system
zeros RAM on startup, you are OK there.
The PPC has a concept of register relative addressing with the .sdata
and .sdata2 sections, but most programs spill out of those areas and you
will still have a relocation problem. Also, some compilers don't take
advantage of those conventions.
What is typically done is to link .data at the desired (RAM) address
and then use the object tools (if you are using gnu tools, objcopy,
etc.) to move the .data section to ROM (flash). In your startup
code, _way early_ and before using any initialized .data values
(perhaps in crt0.s), copy your flash .data image into RAM. This requires
you to know where the .data section starts and how long it is. I'll let
you work out these details (check out the labels etext and edata --
I believe they are useful).
Disclaimer: if you are not using the gnu toolset, everything is likely
to be different but similar. With the gnu toolset, I've inserted just
enough errors to make sure you understand the concepts rather than using
the instructions as boilerplate :-).
gvb
--
+---------------------------------------------------------------------------+
| Jerry Van Baren / vanbaren_gerald@si.com / Grand Rapids Mi / 616-241-7973 |
| My employer is a company. Companies are artifacts of a legal system. |
|________________Artifacts are incapable of having opinions.________________|
> When code is linked with linker script allocating all sections
> including .bss and .data located in flash area and all the sections
> are copied by itself into RAM, how are the data and bss section
> relocated?
Other contributors have addressed the case in which code must work in
either place, using PIC and small data segment registers.
If your .data and .bss are not used until after they are copied to
RAM, then you instruct the loader to resolve all references to them
using their RAM addresses. The GNU loader I use lets me specify this
in the command file. For example, to place the .data section in ROM
but resolve references as though it begins in RAM at address 0x1000, I
use:
.data BLOCK(0x1000) : AT 0x1000 {
*(.data)
}
-=- Andrew Klossner (andrew@cesa.opbu.xerox.com)
"Kim, Jeong-Hwan" <frog@lgic.co.kr> wrote in message
news:UYuy6.305$2b5.9359@news2.bora.net...
> This is a general question about relocation of data and bss section.
>
> When code is linked with linker script allocating all sections including
> .bss and .data located in flash area
> and all the sections are copied by itself into RAM, how are the data and bss
> section relocated?
There's a simple trick to this. If you look at the linker control file
syntax you'll find a way of specifying sections that are to be placed at one
address even though they are to be linked at another. In GNU's ld program
its something like.....
.data SECTION (link address): AT (where you want to put it address)
Since the linker also knows how to work variables and has built-in functions
such as ADDR and SIZEOF its possible to place the sections without having to
compute absolute addresses.
The following is a typical GNU control file for a simple ROM montior that's
starts in ROM but then copies itself to RAM after doing the basic
initializing of the processor. (You may recognize the file names as those
from the 8260 sample startup files.) The bulk of the image starts at offset
0x3000 into the ROM, the RAM image starts at 0x700000 and copied with the
image are data sections. The bss, being unitialized, is not loaded. The
startup code will do a long word copy from end_entry to stext for (edata -
stext) / 4 long words. It will then zero out the bss section by zeroing
everything from edata to ebss. (Those labels can be seen by the code -- you
just reference them like any other external symbol.)
Its worth trying to look this stuff up in the manual.
SECTIONS
{
s1 0xFF800000 :
{
config.o(.text)
}
s2 0xFF800100 :
{
vect_tbl.o(.text)
}
s3 0xFF803000 :
{
2002/09/19 (16:35) from 211.38.3.65' of 211.38.3.65' Article Number : 5920 정지호 Access : 18 , Lines : 31 커널에서 double형의 연산이 가능한가요? 혹시 시도해 보신분 안계세요? 커널 소스에서 double형을 사용한곳을 찾아 보니깐.. 드라이버 쪽에서 몇개가 나오고 나머지는 없더군요... 아래 코드의 수행 결과가 이상합니다.
2002/09/20 (02:44) from 63.220.67.67' of 63.220.67.67' Article Number : 5923 이호 (flyduck@linuxkernel.net) Access : 38 , Lines : 53 Re: 커널에서 double형의 연산이 가능한가요?
원칙적으로 커널에서 floating point 연산을 할 수 없습니다. 이것은 규칙입니다. 요새는 모든 CPU에 실수연산을 담당하는 FPU가 있고, floating point 연산은 이 FPU를 이용합니다. 그리고 이런 연산이 일어날 때마다 FPU register를 조작하게 됩니다. 문제는 커널에서 mode switching이 일어날 때 이 레지스터를 저장하지 않습니다.
따라서 user process에서 실수 연산을 하는 도중에 시스템 콜이나 인터럽트 등으로 커널 모드에 진입을 하고, 커널에서 FPU 레지스터를 바꾼후 그냥 사용자 모드로 되돌아왔다면 사용자 프로그램은 엉뚱한 결과를 낳을 수 있습니다. 눈으로 확인하기는 쉽지 않겠지만요. 그래서 실수 연산과 관련해서는...
- init_module() 처럼 전혀 위험이 없는 곳에 사용하거나 (insmod 프로그램이 실수 연산을 사용하지 않으므로) - 실수연산후 FPU 레지스터의 값을 복구하거나 - software emulation (FPU를 사용하지 않고 소프트웨어적인 실수계산)
2002/09/23 (17:48) from 211.38.3.65' of 211.38.3.65' Article Number : 5931 정지호 Access : 30 , Lines : 93 해결...했습니다. 답변감사합니다.
질문의 코드는 커널 컴파일 할때 -msoft-float 옵션을 켜고 컴파일 할때의 결과 입니다. 그리고 CPU가 FPU가 없는 임베디드 CPU(IBM405GP) 였습니다. (정보가 정확하지 않았죠 ^^)
원인을 추적하던중 gcc 컴파일러의 컴파일러 소스에 버그인지 아닌지 모르겠는 문제의 코드가 있어 수정을 했습니다.
문제의 코드는 libgcc.a를 만드는 소스(soft-float emulation 코드) floatlib.c파일의 double형의 equal(not equal)연산을 하는 코드
int __eqdf2 (double a1, double a2) { //return *(long long *) &a1 == *(long long *) &a2; return (__cmpdf2 ((float) a1, (float) a2) != 0); }
였습니다.
막힌 부분이 원래 코드 입니다.
수정하고 나니... 원래의 코드가 다른 CPU에서는 동작하는지 궁금한데.. 여유가 없어서 테스트를 못해본 상태 입니다. (이상타....혹시 i386계열에서는 문제가 없는거 아닌지.. .. 아~~~ 인텔은 위의 코드를 사용안한다.. 왜~~ FPU가 있으니깐... ^^) 여튼 이렇게 처리하니 문제가 없더군요...
지난 두 장에 이어서 이번에는 메모리에 대한 논의의 후반부인 버쳐메모리를 다루어볼까 합니다. 그동안 소개되었던 수많은 메모리 관리 기법들의 공통분모는 단 한가지로 압축될수 있죠. 바로 그것은 멀티프로그래밍이 가능하도록 최대한 많은 프로세스들이 동시에 메모리에 공존하도록 만드는 것입니다. 이중 버쳐메모리는 일련의 프로세스들이 물리적인 메모리에 할당되지 않아도 실행이 가능하도록 만들어주는 테크닉이며, 가장 큰 장점으로써 물리적 메모리보다 프로그램이 요구하는 메모리가 클지라도 실행이 가능해진다는 점이겠죠. 여기까지는 여러분들이 상식선에서 익히 아시는 부분이라 생각됩니다. 그러나 곧 펼쳐질 버쳐메모리의 해부도를 아는 이들은 그리 많지 않습니다.
버쳐메모리의 의의는 -이거 멋진 말입니다. 맘속에 담아놓고 뽐낼때 씁시다- 메인메모리를 극도로 거대한 동일 어레이(uniform array) 저장매체로 추상화 시킨다는 사실로써 유저의관점에 있어서 물리적 메모리와 논리적 메모리를 완벽하게 분리시켜 버렸다는데에 있죠. 허나, 모든건 장단점이 공존하기 마련이듯, 벼쳐메모리의 오에스 차원에서 구현하기가 먼저 까다롭고 또한 잘못 사용되었을때 성능의 감소는 정말 끔찍하기조차 하죠.
지지난 장에서는 한정된 메모리상에 쏟아지는 프로세스들을 할당하기 위한 기법들로 오버레이(overlay) 또는 다이내믹 로딩(dynamic loading)등이 언급되었었죠. 이들과 버쳐 메모리와는 근본적인 차이점이 존재하게 되는데 후자들이 막말로 자수성가 해야하는 밑바닥 인생들이라면, 버쳐메모리는 오에스라는 엄청난 빽의 지원으로 시작부터 부귀영화를 누리는 귀족이라고 할까요,,,
"On systems which support virtual memory, overlays have virtually disappeared." 아아,,, 기술서가 아닌 문학서를 읽고있다는 착각조차 드는 멋들어진 표현이었습니다.
버쳐메모리를 논하기 위해서는 제일먼저 디멘드 페이징(demand paging)이라는 말부터 살펴볼 필요가 있겠어요. 디멘드 페이징은 버쳐메모리를 구현하기 위한 가장 일반적인 테크닉이죠. 먼저 디멘드 페이징은 전장에서 언급했던 페이징 기법에 전전 장에서 언급했던 스와핑 기법을 적당히 섞은 기법입니다. 여기서 우리는 "혹시 그렇다면 페이징 기법의 쌍벽인 세그멘테이션 기법과 스와핑을 짱뽕시켜도 뭔가 그럴듯하지 않을까!"라고 상상하시는 분이 간혹 계실수 있는데, 정말 예리하시군요,,,맞습니다. 그것또한 버쳐메모리를 구현하기위한 또하나의 테크닉으로써 존재하고 있으며, 그것을 디멘드 세그멘테이션이라고 일컫습니다. 쏟아지는 무관심속에서 장렬하게 쓰러져간 오에스인 OS2가 바로 이렇게 진보적인 메모리 기법을 채용하고 있었죠.
여하튼, 세그멘테이션은 페이징과 달리 그 단위 크기가 불규칙하기에 그 원리가 여러모로 복잡한 관계로 여기서는 디멘드 페이징에 대한 설명만을 하겠습니다. 디멘드 페이징 기법의 원리는 간단합니다. 먼저 우리가 어떤 프로세스를 실행하고자 한다면 간단하게 해당 페이지를 메모리에 스와프 인 시킵니다. 하지만, 여기서 우리는 전체 프로세스에 대응되는 페이지들을 스와프 인 시키는것이 아니라 게으른 스와퍼(lazy swapper)라 불리우는 녀석이 스와핑을 시키는데 해당 페이지가 요구되어지지(demanded) 않을때까지는 절대로 메모리에 스와프 인 시키지 않는 독한 놈입니다. 자, 여기서 우리는 상당히 절묘한 기술용어를 또하나 접하게 되는데 바로 페이지 폴트(page-fault)라는 것이죠. 이경우는 우리의 프로세스군이 엄니의 밥먹으라는 소리에 식탁에 도착했지만 막상 떠먹으려니 주둥이를 방에 두고 왔다는 사실을 깨닫는 순간인것과도 같죠. 전장에서 말씀드렸듯이 페이징 기법은 프로세스의 크기와 상관없이 한개의 프로세스 조차 여러개의 페이지들로 조각조각 나누어버리는 기법인지라, 디멘드 페이징의 경우 프로세스 일부는 오에스로 페이지 폴트 트랩이 보내지기 전까지는 절대로 메모리에 할당되지를 않습니다.
페이지 폴트가 발생한 상황에 대한 대처는 비교적 심플합니다. 제일먼저 페이지 폴트가 발생하면 프로세스로 부터 해당 메모리로의 레퍼런쓰가 옳았는지부터 확인을 하게 되고, 만일 잘못된 레퍼런쓰였다면 프로세스를 종료시켜주게 되죠. 바로 폭탄맞는 경우입니다. 이경우는 프로세스 자체의 오류이기 때문에 오에스도 어쩔수가 없겠죠. 하지만, 만일 제대로된 레퍼런스였고, 해당 페이지가 메모리에 할당이 되지 않은 상태였다면은 빈 프레임(페이지와 프레임은 각각 논리적 메모리와 물리적 메모리의 단위를 일컫죠)을 재빨리찾아내어 해당 페이지를 프레임에 할당하게 되고, 비로서 아까 중간에 멈추었던 인스트럭션을 재실행! 하게 되는것이죠. 위와같은 식으로 메모리에 단 한개의 페이지를 할당하지 않고서도 결국 프로세스를 실행할수 있게 만드는 무서운 기법이 가능해질수도 있을진데, 바로 순수 디멘드 페이징(pure demand paging) 기법입니다.
위에서 비교적 심플한 방법으로 대처한다고는 막상 말했지만, 이것을 현실적으로 구현한다는 측면에서는 아주 골치아픈 첨병이 숨어있어요. 페이지 폴트의 발생시 프로세스는을 재실행 한다고 했는데, 재실행 한다는 뜻은, 바로 이전까지의 자신의 위치, 레지스터 정보등의 상태를 저장해야 한다는 사실을 암시하게 되지요. 또한 여러게의 프로세스들이 연속적으로 페이지 폴트를 쎄려버리면 씨피유,,,정말 뚜껑 열리게 되겠죠. 허나 다행히도 메모리에서는 신비한 현상이 한가지 일어나는데 바로 레퍼런스의 지역성(locality of reference)로써 후반부에서 다시 다루도록 하겠습니다. 결론적으로 그 지역성 덕분에 매번 한순간 다발적으로 페이지 폴트가 일어나는 일은 극히 드물게 됨으로 나름대로 효율성을 기할수가 있게 되었죠.
자, 디멘드 페이징 특유의 상황이 또한가지 연출되는데 바로 페이지 바꿔치기(page replacement)입니다. 이경우는 페이지 폴트가 발생해서 해당 페이지를 막상 할당하려고 보니 빈 프레임이 하나도 없드라,,,고로 현재 할당되어있는 녀석들중 하나와 바꿔치기 해서라도 올려주는 것이죠. 이때 과연 그렇다면 어떤 녀석을 다시 디스크로 끌어내리는 희생양으로 삼을지가 또한 여러개의 알고리즘들로 존재하고 있고, 이들 알고리즘들과는 별도로 페이지 바꿔치기를 함에있어서 참으로 드러진 기법이 등장하게 되는데 바로 더티 비트(dirty bit)의 활용입니다. 자, 한번 생각해 보셔요. 페이지 바꿔치기가 일어나려면 2번의 I/O가 필요하겠죠? 먼저 현재 프레임에 할당된 페이지중 한개의 희생양을 디스크로 끌어내릴때 한번, 그리고 디스크에서 요구된 페이지를 프레임으로 올릴때 한번,,,이렇게 총 두번입니다. 이러한 I/O는 순전히 오버헤드로 작용하여 시스템의 성능을 감소시키게 되겠죠. 하지만, 더티 비트라는 비트를 한개의 프레임마다 달아놓고 만일 해당 프레임의 내용이 바뀌었을경우 1로, 그렇지 않을경우 0으로 세팅한다고 약속을 하게되면,,,한번의 I/O로도 바꿔치기가 가능해집니다. 왜냐하면 더티 비트가 0인 녀석의 경우 디스크로 다시 내려올 이유가 전히어~ 없어지기 때문입니다. 뭣하러 내려오남,,,백업본이 고스란이 이미 디스크에 있는디,,,
페이지 바꿔치기를 위하여 희생양 프레임을 고르는데 사용할수 있는 대표적인 알고리즘들을 이제는 살펴봅시다.
1. FIFO 알고리즘 : 곰곰히 생각하면 FIFO라는 말은 이제 우리에게 익숙해졌을 정도로 도처에 등장하는 감초알고리즘같습니다. 말그대로 이 알고리즘은 프레임에 올라온지 가장 오래된 페이지를 내려버리죠. 이 알고리즘을 구현하기 위해서는 반드시 각 페이지가 올라온 시간을 나름데로 트래킹하고 있어야 할듯 하지만, 그 방법보다는 FIFO큐(Queue)를 구성해서 순서만을 트래킹하는 방법을 사용하게 됩니다. FIFO알고리즘의 성능은 언제나 좋지만은 않지요. 더구나 아주 요상한 현상이 본 알고리즘에서는 발생할수 있는데, 바로 벨로디의 요상함(Belody's anomaly)라는 현상으로써 프레임의 수를 늘렸음에도 오히려 페이지 폴트수가 증가할수도 있는 현상을 나타내주기도 해요. 즉, 램을 올렸는데 이게 어떻게된건지 오히려 가끔씩 디스크가 더욱 버벅거리게 되는격이죠. 본 현상은, 프레임수가 늘어나면 페이지 폴트가 줄어들것이라는 상식적인 실험을 하는 와중에 언제나 그렇지가 않다는 사실을 연구하다 졸지에 발견한겁니다. 상식적인 가정을 의심하는 가운데 빛나는 논문이 여러분을 기다리죠,,,-_-+
2. 최적(Optimal) 알고리즘 : 이것은 앞으로 장시간 동안 사용되지 않을 페이지를 프레임에서 내려버린다는 알고리즘입니다. 말그대로 최적이죠. 하지만 가장큰 단점은 구현이 불가능하다는 것입니다. 미래에 어떤 페이지가 어떻게 안쓰일지를 무슨수로 결정하겄습니까,,,결론적으로 특정 알고리즘의 성능을 측정함에 있어서 이론적 비교의 대상으로 주로 사용됩니다.
3. LRU(Least Recently Used) 알고리즘 : 꽁머리대신 닭대가리라고, 최적알고리즘은 미래의 현상에 대한 판단이 필요하다면, 요 알고리즘은 과거의 행적에 대한 판단을 기준으로 페이지 바꿔치기를 하게 되요. 즉, 가장 오랫동안 침묵을 지키던 페이지를 내려버리죠. 구현을 위해서는 카운터 또는 스택을 이용해서 얼마나 오랫동안 개겼는지를 트래킹하게 됩니다. 최적 알고리즘과 LRU알고리즘에는 벨로디의 요상함 현상이 일어나지 않죠. 허나, 최적은 구현이 불가능하고, LRU는 특유의 오버헤드가 많은데다가 하드웨어의 특별한 지원까지 필요로 하게되는 단점을 가지고 있습니다.
4. LRU Approximation 알고리즘 : LRU가 그래도 좋긴 좋은가 봅니다. 이것의 구현이 까다로우니 흉내라도 내겠다는 알고리즘이죠. 흉내를 내는 여러자기의 알고리즘들이 있습니다만, 그중에 하나 주목할 만한 것이 있는데, 바로 Enhanced Second-Chance 알고리즘입니다. Second-Chance 알고리즘은 기본적으로 FIFO알고리즘의 골격에 큐의 꼬리와 머리를 연결하여 원형 순환을 시킨뒤 각각의 큐옆에 레퍼런스 비트(reference bit)를 달아주는 형태를 하고 있어요. 레퍼런스 비트가 0일경우는 바꿔치기를 하고, 만일 1일경우는 0으로 리셋되는 대신에 다음 프레임으로 넘어가게 되는것이죠. 만일 0으로 리셋된 프레임이 히트되었을경우는 0을 다시 1로 전환시켜주겠죠? 본 알고리즘의 핵심은 말그대로 두번째 기회를 주게 된다는 것이며, 이렇게 한번 넘어가게 된다면 포인터가 전체 큐를 한번 싸그리 돌기전까지는 안전하게 프레임에 남아있게되죠. 본 알고리즘이 최악의 경우 어떤 현상을 나타낼듯한가요? 바로 2번 뺑이치는 FIFO그 이상 이하도 아니겠죠.
그렇다면 Enhanced Second-Chance이란? Ehhanced는 위에 간략하게 설명한 알고리즘에 또하나의 비트를 추가해주게 됩니다. 바로 더티비트! 이죠. 더티비트와 레퍼런스 비트의 조합에는 다음과 같은 경우의 수가 나오게 됩니다.
(레퍼런스비트, 더티비트) (0,0):최근에 사용되지도 않았을뿐더러 변하지도 않났다네,,,:이거, 당장 방빼야하는 페이지입니다. (0,1):최근에 사용되지는 않았지만, 내용이 조금 변하긴 했지,,,:썩 빼기 좋은 경우는 아닙니다만(2번의 I/O가 필요하죠) 최악의 경우 이녀석도 방빼게 됩니다. (1,0):최근에 사용되었지만, 내용은 그대로라네,,,:아마도 곧 다시 사용되겄죠,,,빼기가 참 그렇군요,,, (1,1):최근에 사용되었을뿐더러 내용까지 변했다네,,,:곧 다시 사용될듯하며, 내리기 전에는 반드시 디스크에 기록을 해야할겁니다.
참고로 우리의 호프 매틴토시의 버쳐메모리 매니져가 위의 알고리즘을 사용하고 있습니다.
그밖엔 카운팅 알고리즘, 페이지 버퍼링 알고리즘등이 책에 소개가 되지만, 핵심은 위에서 소개가 다 된듯하군요. 이것으로 페이지 바꿔치기에 대한 고찰이 끝난것이 아닙니다. 페이지 바꿔치기를 함에있어서 또하나의 기준으로 글로벌(global) 이냐, 아니면 로컬(local)이냐하는 논의가 있죠. 전장에서 페이징을 하기 위해서는 한개의 프로세스조차 조각낸다는 말을 한적이 있죠? 즉 한개의 프로세스가 10조각으로 나위었을때 5조각을 올린다음 페이지 폴트가 발생했다고 가정해봅시다. 만일 이때 5조각중 한조각을 내린뒤 필요한 한조각을 올리게 될경우(이럴경우 해당 프로세스의 조각은 5개로 불변이죠)가 바로 로컬 바꿔치기 알고리즘이고, 다른 프로세스의 페이지를 내린뒤 한개를 올려서 총 6개의 조각을 만들어주는것이 글로벌 바꿔치기 알고리즘이죠. 결과적으로 글로벌 페이지 바꿔치기는 시스템 전체적인 프로세스 처리량을 향상시켜주기는 하지만, 프로세스가 자기 자신에 대한 페이지 폴트율을 도무지 제어하기가 어려워 진다는 단점이 있습니다만 현재 대부분의 버쳐 메모리가 글로벌 바꿔치기 알고리즘을 사용합니다.
가끔씩, 버쳐메모리를 사용할때,,,자신의 하드가 갑자기 미쳐서 쉴새없이 드르르르륵~~~거리며 사람을 화들짝 놀라게 만드는 경우를 혹시 경험해 보셨나요? 그 현상에 대한 정확한 설명을 여기서 드립니다. 바로 트래슁(thrashing)이라 하는 현상이거든요.
오에스는 언제나 자신의 시스템에 대한 효율을 기하기 위하여 씨피유사용량(CPU Utilization)을 점검하곤 하지요. 만일 씨피유사용량이 낮아졌다는 판단이 들경우에는 멀티프로그래밍의 수준을 높여줌으로써 언제나 적정한 사용량을 유지하도록 분위기를 조성해줍니다. 그런데, 버쳐메모리가 인스톨된 시스템에서 어느 프로세스가 실행됨에 있어서 새로운! 페이즈(phase:여기서는 늘 하던 일이 아닌 뭔가 새로운 일을 하려는 경우로 이해하시길)로 전환되게 될경우 프레임에 해당 페이지가 할당되어 있지 않다면 당연히 페이지 폴트가 발생하기 시작하겠죠. 이때 페이지 발생하는 폴트의 결과로 다른 프로세스의 프레임을 점유하게 되면, 다른 프로세스 또한 페이지 폴트율이 증가하게 되고 이런식으로 서로의 페이지 폴트율이 증가하기 시작합니다. 전체적인 페이지 폴트율이 증가하면서 페이징 디바이스의 큐또한 기하급수적으로 쌓여가고 반면에 레디큐는 텅텅,,,비어가죠. 레디큐가 비어간다는 것은 현재 실행중인 프로세스가 줄어든다는 곳이고 이는 과연 무엇을 뜻하게될까요,,,바로 씨피유사용량이 낮아졌다는 뜻입니다. 고로 씨피유는 이러한 상황에서 눈치없이 멀티프로그래밍레벨을 올려버릴테고,,,그 결과는 ? 정말 참담합니다,,,더더욱 낮아지는 레디큐와 자꾸만 올라가는 멀티프로그래밍 레벨,,,고로 결국은 작은 눈한덩이가 눈사태를 일으키듯이 모든 프로세스는 서로 페이징하느라 아무일도 못하게 되죠. 이러한 현상이 바로 트래슁이랍니다. 트래슁을 미연에 방지하는 궁극의 테크닉은 바로 로컬 페이지 바꿔치기 알고리즘이지만, 각 프로세스에게 과연 얼마만큼의 프레임을 지정해줄것인가에 대한 문제또한 만만한것이 아니죠. 물론 시스템의 프로세스 처리량의 감소도 감수해야 합니다.
트래슁을 방지하기 위하여 프로세스당 적정한 프레임수를 결정할수 있는 방법에는 워킹셋전략(working-set strategy)이라는 것이 있지요. 본 기법의 핵심은 프로세스 실행에 있어서 로컬리티 모델(locality model)에 대한 정의인데, 여기서는 이 로컬리티 모델만을 간략하게 다루겠습니다. 로컬리티 모델이란 은 메모리상에서의 정보의 변화를 표시해주는 지도에서 확인이 되는 현상으로 x축을 시간, y축을 메모리 어드레스로 설정하고 플로팅을 했을때 2차원 평면상에 주기적인 패턴이 나타나게 되는 것입니다. 만일 이 평면이 무작위적인 정보가 왔다갔다한다면 전체적으로 흑과 백으로 회색을 디더링한것과 같은 결과가 나오겠지만, 부분 부분 만이 패턴을 그리며 연결된 무늬가 형성된다는 점에서 분명히 메모리 주소값들의 사용은 어떤 패턴을 가지고 있다는 사실을 암시해주죠. 이 사실이 뭐가 그리 중요하냐구요? 바로 이것이 캐쉬메모리가 존재하도록 만든 배경이론입니다. 매번 다른 인스트럭션과 다른 데이터들이 메모리와 씨피유 사이를 관통한다면 캐쉬메모리는 그 존재의미가 전혀 없어져 버리죠.
이상으로 버쳐메모리에 대한 이론적인 배경을 살펴보았습니다. 메모리에 대한 논의를 완전히 끝내기 전에 한가지 지난번 다루지 못한 부분이 있기에 잠시 언급을 할까 해요. 바로 리엔트랑코드(reentrant code)입니다. 리엔트랑코드는 앞장의 페이징과 세그멘테이션 기법을 논의하면서 특정 페이지와 세그멘트를 공유할때 등장하는 말이죠. 세그멘테이션 에서 에디터의 예를 들면서 에디터 세그먼트와 데이터 세그먼트로 나눌경우 두사람이 각자의 데이터 세그먼트를 메모리에 할당하되, 에디터 세그먼트는 메모리에서 공유가 가능해진다고 했었죠. 이때 에디터 세그먼트가 공유되기 위해서는 바로 리엔트랑코드로 만들어져야 합니다. 리엔트랑 코드는 간단하게 말한다면 자신을 변화시키지 않는(non-self-modifying) 코드입니다. 즉 실행되는 동안은 자신을 절대로 변화시키지 않죠. 이것은 아주 교묘하게도 예전에 말씀드렸던 데이터의 싱크(Sync:Syncronization)문제와도 연관이 되어있는 있는문제로, 최근에 오에스가 고차원적으로 발전하며 자주 등장하는 말입니다. 리엔트랑코드,,,그냥 우리와 아무 상관없는 이야기로 들리겠지만, 여러분 애플의 카본(Carbon)의 정체가 무엇인줄 아십니까? 바로 애플의 툴박스 루틴들중 리엔트랑코드만을 골라내거나 또는 리엔트랑코드로 재작성된 코드들의 묶음입니다.
이것으로 메모리에 대한 기나긴 논의를 마치게 되겠군요. 전혀 아쉽지가 않은 이유를 통 모르겄네,,,-_- 이제는 제가 기획한 오에스 개론의 끝이 보이는군요. 이제남은 것은 파일, 쓰레드, 그리고 통신의 기초라 할수 있는 OSI 7Layer Protocol을 한장내외로 아주 간략하게 설명한뒤 강의를 마칠까 합니다. 그럼 또 봐유,,,
- Microsoft Win32 Device Driver Kit (DDK) for Windows NT, versions 3.1, 3.5, 3.51, 4.0 - Microsoft Win32 Device Driver Kit (DDK) Windows 2000
[요 약]
Windows NT 와 Windows 2000 에서 커널 모드 드라이버는 사용자 모드 응용 프로그램으로 콜백(Callback) 할 수 없습니다. 이것은 의도적으로 설계된 것입니다. 비동기적인 이벤트에 대해서 응용 프로그램에게 알리기 위한 드라이버를 위하여, 응용 프로그램은 이벤트가 발생할 때 마다 드라이버가 해당 요구(Request) 를 완료할 수 있도록 모든 시간에 대해서 드라이버에 걸려 있는 입출력 요구(I/O Request) 를 유지할 필요가 있습니다. 본 문서는 응용 프로그램과 드라이버가 비동기적인 알림을 수행하기 위해서 사용할 수 있는 전형적인 방법에 대해서 다루고 있습니다.
[추가 정보]
응용 프로그램
응용 프로그램은 전용 입력 스레드를 가질 수 있습니다. 해당 스레드는 입출력 요구(I/O Request) 를 보내고 응답을 대기하는 루프(Loop)를 돌게 됩니다. 드라이버가 열려 있고 핸들인 hDevice 를 가지고 있다면 해당 루프(Loop)는 다음과 같을 수 있습니다.
while (!ApplicationExiting) {
returnval = DeviceIoControl (hDevice, dwIoControlCode, lpvInBuffer, cbInBuffer, lpvOutBuffer, cbOutBuffer, lpcbBytesReturned, lpoOverlapped); if (!returnval && (GetLastError() == ERROR_IO_PENDING)) { WaitForSingleObject (hEvent, INFINITE) // hEvent is located in overlapped structure as well ... // Code to do action ResetEvent (hEvent) } { ... // Code to handle other situations } }
BOOL 형인 ApplicationExiting() 는 이벤트에 대한 검사를 중지하도록 하는 루프(Loop)에 대한 조건을 나타내고 있습니다. 응용 프로그램의 메인 스레드는 종료할 시간이 되었을 때 이 BOOL 을 TRUE 로 설정할 수 있습니다. I/O 제어 코드 dwIoControlCode()는 드라이버에 의해서 정의됩니다.
위의 DeviceIoControl 호출은 요구(Request)가 지연(Pending)되고 있는 동안 다른 응용 프로그램 스레드가 이 요구(Request)를 다른 드라이버로 보내는 것을 지속할 수 있도록 하기 위해서 비동기적으로 만들어져야 합니다. DeviceIoControl 호출의 오버랩 된 구조 내에서 초기화되고 배치된 이벤트는 요구(Request)의 완료로 인해서 이 스레드를 동기화 하기 위하여 사용될 수 있습니다. 그러한 이벤트가 만족되면 이러한 스레드는 다른 응용 프로그램의 스레드에게 해당 이벤트가 시그널(Signal) 되었음을 알릴 수 있습니다. 오버랩 된 구조가 지정되지 않았다면 이 요구(Request)가 해당 드라이버내에서 처리되는 동안 다른 모든 스레드는 블록(Block)화 될 것입니다. 다른 스레드는 동기적인(Synchronous) DeviceIoControl 이 완료될 때 까지 릴리즈 되지 않을 것입니다.
해당 드라이버가 비동기적인 이벤트 알림 내에서 전송을 위한 Read 요구(Request)를 사용한다면 사용자 모드(User-Mode) 스레드는 DeviceIoControl() 을 대신하여 ReadFile() 또는 ReadFileEx() 을 사용할 수 있습니다.
드라이버
해당 드라이버는 이벤트가 일어나기 전까지는 입출력 요구(I/O Request) 를 완료하지 못하게 됩니다. 해당 드라이버가 입출력 요구(I/O Request)를 받았을 때 이벤트가 발생했고 이벤트가 응용 프로그램으로 전송되기 위해서 대기하고 있다면 그 드라이버는 Dispatch 루틴(Routine) 내에서 해당 요구(Request)를 완료할 수 있습니다. 대기하고 있는 이벤트가 없다고 보고되면 그 드라이버는 다음의 단계들을 수행합니다:
1. IoMarkIrpPending()을 사용하여 Irp pending 을 표시합니다.
2. IoSetCancelRoutine()을 사용하여 Irp 에 대한 취소 루틴(Routine)을 설정합니다.
3. Irp 를 저장소(예를 들어서 Queue)에 넣습니다.
4. Dispatch 루틴(Routine)으로부터 STATUS_PENDING 을 반환합니다.
나중에 이벤트가 발생하였을 때 해당 드라이버는 그것의 Deferred Procedure Call (DPC) 루틴(Routine)으로부터의 지연 요구(Request)를 완료할 수 있습니다. Irp 가 완료되기 이전에 해당 드라이버는 IoSetCancelRoutine 을 이용하여 취소 루틴(Routine) 주소를 NULL로 설정해야 합니다.
A device driver should avoid polling its device unless absolutely necessary and should never use a whole timeslice polling. Polling a device is an expensive operation that makes any operating system compute-bound within the polling driver. A device driver that does much polling interferes with I/O operations on other devices and can make the system slow and unresponsive to users.
Recently developed devices, which are as technologically advanced as the processors on which Windows NT®/Windows® 2000 is designed to run, seldom require a driver to polls its device to ensure that the device is ready to start an I/O operation or that an operation is complete.
Nevertheless, some devices still in use were designed to work with old processors, which had narrow data buses, slow clock rates, and single-user, single-tasking operating systems that did synchronous I/O. Such devices might require polling or some other means of waiting for the device to update its registers, particularly for Windows NT/Windows 2000, which is designed to do asynchronous I/O on new processors with wide data buses and fast clock rates.
Although it might seem logical to solve a slow-device problem by coding a simple loop that increments a counter, thereby "wasting" a minimum interval while the device updates registers, such a driver is unlikely to be portable across Windows NT/Windows 2000 platforms. The loop counter maximum would require customization for each Windows NT/Windows 2000 platform. Furthermore, if the driver is compiled with a good optimizing compiler, the compiler might remove the driver's counter variable and the loop(s) where it is incremented.
Follow this implementation guideline if the driver must stall while the device hardware updates state:
A driver can call KeStallExecutionProcessor before it reads the device register(s). The driver should minimize the interval it stalls and should, in general, specify a stall interval no longer than 50 microseconds.
The granularity of a KeStallExecutionProcessor interval is 1 microsecond.
If the device frequently requires more than 50 microseconds to update state, consider setting up a device-dedicated thread in the driver.