ARM System Developer's Guide
- Designing and Optimizaing System Software
#####
The Memory Hierarchy and Cache Memory
Cache Architeture
Cache Policy
Coprocessor 15 and Caches
Flushing and Cleaning Cache Memory
Cache LockDown
Caches and Software Performance
### 12.4. Coprocessor 15 and Caches
# CP15 : 캐시를 가지는 ARM 코어를 설정하고 제어하기 위해 사용
- c7, c9 : cache setup 및 cache 동작을 제어
- 보조 CP15:c7 : write only, 캐시 clean, flush에 사용
- CP15:c9 : victim pointer base address를 정의. 캐시 안에서 lock 되어있는 코드 또는 데이터 라인수를 결정
### 12.5. Flushing and Cleaning Cache Memory
> C로 인라인 함수로 작성하여 하나로 include하면 더욱 간결해 질 수 있음
__inline void flushCache926(void)
{
unsigned int c7format = 0;
__asm{MCR p15, 0, c7format, c7, c7, 0};
}
__inline void flushDCache926(void)
{
unsigned int c7format = 0;
__asm{MCR p15, 0, c7format, c7, c6, 0};
}
__inline void flushICache926(void)
{
unsigned int c7format = 0;
__asm{MCR p15, 0, c7format, c7, c5, 0};
}
## 12.5.2. Cleaning ARM Cached Cores
- 캐시 클린?
캐시 컨트롤러에게 모든 D캐시라인을 메인 메모리에 쓰도록 명령하는 것
- 캐시라인의 dirty bit status bit들을 0으로 클리어
- write back 방식의 캐시에서만 적용이 가능
- 때때로 clean이라는용어대신 writeback이나 copyback 이라는 용어가 사용되기도 함
=> 캐시에 write back 방식을 적용한다는 의미는 캐시 clean이라는 의미와 동일
* ARM이 아닌 곳에서는 flush라는 용어가 ARM에서 사용하는 clean이라는 의미로 사용되기도 함.
## 12.5.3. Cleaning the D-Cache
- 프로세서마다 D캐시를 clean하는 명령어가 다르므로 D 캐시를 clean하는 방법은 3가지로 나눌 수 있음
> D 캐시 clean 방법
- cleanDCache : 모든 D캐시를 clean
- cleanFlushDCache : 모든 D캐시를 clean하고 flush
- cleanFlushcache : I캐시와 D캐시를 모두 clean하고 flush
- 헤더파일의 모든 값들은 2의 로그로 표현된 크기 또는 field locator임
- 만약 값이 locator라면, CP15 레지스터 안에 있는 비트필드의 최하위 비트들을 나타냄
ex. 상수 17WAY : CP15:c7:c5 레지스터 안에 way 선택 필드의 최하위 비트를 가리킴
- 17WAY 값은 clean 명령어가 MCR 명령어를 사용하여 호출될 때, CP15:Cd:Cm 레지스터 쪽으로 이동되는
코어 레지스터(Rm)의 비트 조작을 지원하기 위해 이러한 형식으로 저장 ????
CSIZE | 캐시 크기에 밑이 2인 로그를 취한 값. 바이트. (즉, 캐시 크기는 (1<<CSIZE) 바이트 임) |
CLINE | 캐시라인의 길이에 밑이 2인 로그를 취한 값. 바이트. (즉, 캐시라인의 길이는 (1 << CLINE) 바이트 임) |
NWAY | ways의 수를 의미하며 세트 연상과 동일 |
17SET | CP15:c7 명령 레지스터 안에서 세트 인덱스가 왼쪽으로 시프트될 비트 수를 의미 순차적으로 캐시를 액세스할 때 CP14:c7 레지스터의 세트 인덱스 부분을 증가시키거나 감소시키기 위해 사용 |
17WAY | CP15:c7 명령 레지스터 안에서 way 인덱스가 왼쪽으로 시프트 될 비트 수를 의미 순차적으로 캐시를 액세스 할 때 CP15:c7 레지스터의 way 인덱스 부분을 증가시키거나 감소시키기 위해 사용 |
19WAY | CP15:c9 명령 레지스터 안에서 way 인덱스가 왼쪽으로 시프트 될 비스 수를 의미 순차적으로 캐시를 액세스 할 때 CP15:c9 레지스터의 way 인덱스 부분을 증가시키거나 감소시키기 위해 사용 |
> 코어 특정 데이터에서 계산되는 2가지 상수
SWAY | way의 크기에 밑이 2인 로그를 취한 값. 바이트. (way의 크기는 (1 << SWAY) 바이트 임) |
NSET | way 당 캐시 라인의 수를 의미. 세트 인덱스의 크기에 밑이 2인 로그를 ㅇ취한 값 (세트의 값은 (1 << NSET) ) |
## 12.5.4. Cleaning The D-Cache Using Way and Set Index
- WAY 및 세트 인덱스 주소를 사용해서 D-캐시 클린하기
- ARM 코어에서는 캐시안에 해당 위치를 찾기 위해 way와 set index를 사용하여 하나의 캐시라인을 clean하고 flush를 수행 함
> way를 이용하여 캐시라인을 클린하고 플러시하는 데 사용하는 MRC명령어
- 명령어 캐시라인 플러시
- 데이터 캐시라인 플러시
- 데이터 캐시라인 클린
- 데이터 캐시라인 클린 및 플러시
- 코어는 해당 way와 set index address를 가지고 각 캐시라인을 선택
- 코어 레지스터 Rd에 저장되어 있는 값은 동일한 프로세서에 대하여 모두 동일하지만,
레지스터 안에 있은 비트필드의 형식은 프로세스마다 다름
- 명령어를 실행하기 위해서는 코어 레지스터 Rd 안에 저장되는 값을 적절한 CP15:c7 레지스터 형식에 맞게 생성
- 일반적으로 "way를 선택하는 영역 + way 안에 세트를 선택하는 영역"의 두 필드를 포함
- 레지스터가 생서되면 Rd를 CP15:c7 레지스터로 이동하기 위해 적절한 MCR(coprocessor -> register)를 사용
> Example 12.3 <
- way와 set index를 사용한 캐시 flush, clean, flush and clean을 수행
- CACHECLEANBYWAY 매크로 사용
- CP15:c7 레지스터(c7f) 안에 프로세서 레지스터를 빌드하기 위해 cache.h를 include하여 상수를 사용
- 레지스터 c7f를 0으로 설정
: 선택된 동작을 실행하기 위해 MCR 명령어 안에 있는 Rd 입력값으로 사용
- 매크로는 각각의 쓰여진 캐시 라인에 대해 형식에 따라 레지스터 c7f를 증가시킴
: 내부 loop문에 있는 set index와 외부 loop문에 있는 way index가 증가
- 중첩 loop를 이용해 모든 way에 있는 모든 캐시 라인들이 클릭 됨
AREA cleancachebyway , CODE , READONLY ; 영역의 시작
...
EXPORT cleanDCache
EXPORT cleanFlushDCache
EXPORT cleanFlushCache
INCLUDE cache.h
c7f RN 0
MACRO
CACHECLEANBYWAY $op
MOV c7f, #0 ; c7f 생성
5
IF "$op" = "Dclean"
MCR p15, 0, c7f, c7, c10, 2
ENDIF
IF "$op" = "Dcleanflush"
MCR p15, 0, c7f, c7, c14, 2
ENDIF
ADD c7f, c7f, #1<<17SET ; set index 1 증가
TST c7f, #1<<(NSET+17SET) ; index의 overflow 테스트
%BT5
BIC c7f, c7f, #1<<(NSET+17SEt) ; index의 overflow 클리어
ADDS c7f, c7f, #1<<17WAY ; victim 포인터 1 증가
BCC %BT5 ; way overflow 테스트
MEND
cleanDCache
CACHECLEANBYWAY Dclean
MOV pc, lr
cleanFlushDCache
CACHECLEANBYWAY Dcleanflush
MOV pc, lr
cleanFlushCache
CACHECLEANBYWAY Dcleanflush
MCR p15, 0, r0, c7, c5, 0 ; I캐시 플러시
MOV pc, lr
...
## 12.5.5. Cleaning The D-Cache Using The Test-Clean Command
- 테스트 클린 명령어를 사용하여 D 캐시 클린하기
- 최근의 ARM 코어, ARM926EJ-S, ARM1026EJ-S는
CP15:c7레지스터의 clean 여부를 테스트하는 방법을 사용하여 캐시를 clean하는 명령어를 지원
=> "test-clean", 소프트웨어 루프에서 사용할 때 효율적으로 캐시를 클린할 수 있는 특별한 클린 명령어
- ARM926EJ-S, ARM1026EJ-S는 way 및 세트 주소지정방식을 사용하여 clean할 수 있지만 "test-clean"을 이용한 방법이 보다 효율적임
> ARM926EJ-S, ARM1026EJ-S 코어를 클린하기 위한 명령어
> Example 12.4 <
: ARM926EJ-S, ARM1026EJ-S 프로세서를 위한 cleanDCache, cleanFlushDCache, cleanFlushCache 루틴
- 첫번째의 dirty cache line을 찾아 내용을 메인메모리로 전송하여 클린 작업을 수행
- 만약 dirty cache가 더 존재한다면, Z 플래그가 0으로 설정
- Z 플래그를 테스트하고, 반복을 위해 이전 부분으로 다시 분기하여 D 캐시가 clean 될 때까지 반복하여 수행
* test-clean 명령어는 PC(r15)를 MCR 명령어의 입력 레지스터 Rd로 사용
## 12.5.6. Cleaning The D-Cache in Intel XScale SA-110 and Intel StrongARM Cores
- 인텔 XScale SA110과 인텔 StrongARM 코어에서의 D캐시 클린하기
- cleanFlushcache(I캐시와 D캐시를 모두 clean하고 flush) 방법을 사용
- XScale에는 프로세서에는 라인을 채우지 않고 D 캐시에마는 라인을 할당하는 명령어가 있음
-> 해당 명령어를 수행하면 유효비트가 1로 설정되고 Rd 레지스터 안에 제공된 캐시태그를 가지고 디텍토리 엔트리를 채움
명령어가 수행될 때는 메인메모리에서 데이터가 전송되지 않음
=> 캐시안에 있는 데이터는 프로세서에 의해 쓰여질 때 초기화 됨
* dirty 상태인 캐시라인을 없애는데 이점이있음
- StrongARM과 XScale 프로세서는 그 캐시를 clean 하기 위해 추가적익 기법이 필요
=> 캐시를 clean하기 위해 사용되지 않응 캐시된 메인메모리의 전용 영역을 필요로 함 => ????, 전용 영역을 어떻게 쓰는거지?
- StrongARM과 XScale 프로세서는 메모리의 고정된 블록을 읽어 clean을 수행
=> 프로세서들이 라운드로빈 정책을 사용하기 때문
- 코어에서 캐시 크기와 동일한, 캐시된 메인메모리 블록을 연속적으로 읽을 때,
- 읽혀진 값들은 현재 모든 캐시 라인에 있는 값을 없애고 메인메모리로 읽혀저 저장
- 읽혀진 데이터 열이 모두 쓰여지면 그 안에 유용하지 않은 정보들을 가지고 있음
=> 전용 메모리 블럭은 그 안에 유용하지 않은 정보들을 포함하므로
- 캐시는 유용한 캐시된 데이터를 잃어 버릴수 있는 것을 모르고 flush 될 수 있음
=> StrongARM과 XScale에서 이 방법을 이용하여 캐시 cleaning을 수행
: cleanDCache, cleanFlushDCache, cleanFlushCache, cleanMiniDCache(XScale에서 작은 D캐시 clean에 사용)
> Example 12.5 <
- CPWAIT, CACHECLEANXSCALE 매크로
> CPWAIT : XScale에서 CP15 연산이 부작용 없이 수행되는지 보장하기 위해 사용되는 3개의 명령어 코드
CP15 명령어가 완료되고 파이프라인안에 명령어들이 모두 클리어 될 때까지의 충분한 프로세스 사이클을 보장하기 위해 수행 됨
MACRO
CPWAIT
MRC p15, 0, r12, c2, c0, 0 ; CP15를 읽음
MOV r12, r12
SUB pc, pc, #4 ; 다음 명령으로 분기
MEND
> CACHECLEANXSCALE : cleanDCache, cleanFlushDCache, cleanFlushCache 루틴 생성
.....
## 12.5.7 Cleaning and Flushing Portions of a Cache
- 캐시의 일부분을 클린하고 플러시하기
- ARM core는 메인메모리 안에서 표현되고 있는 위치를 참조하여 하나의 캐시라인만을 clean 및 flush 할 수 있음
> MCR 명령어 메인메모리 안에서 위치를 참조하여 캐시라인을 clean 및 flush하는 명령어
> 메인 메모리 안에서 원래 값으로 참조된 캐시 라인을 clean 하고 flush할 때의 CP15:c7 레지스터 포맷
- 명령어를 사용할 때 코어 레지스터 Rd에 저장된 값은 같은 프로세서하에서는 네 명령어 모두 동일하지만,
CP15:c7 레지스터 안에 있는 비트값의 포맷은 프로세서들 마다 다름
- 코어가 MMU를 가진다면 수정된 가상 주소에 의해,
- 코어가 MPU를 가진다면 물리 주소에 의해,
- 캐시라인을 clean하고 flush하는 코어를 위핸 레지스터 포맷을 나타냄
- 메모리의 한 영역을 나타내는 캐시 안에 캐시라인을 클린하거나 플러시하는 6개의 루틴을 만들기 위해 4개의 명령어를 사용
flushCacheRegion | 메인메모리의 한 영역을 나타내는 I 캐시로부터 캐시라인을 플러시 |
flushDCacheRegion | 메인메모리의 한 영역을 나타내는 D 캐시로부터 캐시라인을 플러시 |
cleanDCacheRegion | 메인메모리의 한 영역을 나타내는 D 캐시로부터 캐시라인을 클린 |
cleanFlushDcacheRegion | 메인메모리의 한 영역을 나타내는 D 캐시로부터 캐시라인을 클린하고 플러시 |
flushCacheRegion | I 캐시와 D 캐시 둘 다로부터 메인메모리의 한 영역을 나타내는 캐시라인을 플러시 |
cleanFlushCacheRegion | D 캐시를 클린하고 플러시 한 다음, I 캐시를 플러시 |
- 함수의 파라미터 : 메인메모리에 있는 시작주소(adr). 바이트로 나타낸 영역의 크기(b)
* 캐시 영역의 함수의 사용은 작은 메모리 영역에 대해서는 매우 성공적임
### 12.6. Cache LockDown
int interrupt_state; /* FIQ와 IRQ 비트들의 상태를 저장 */
int glovalData[16];
unsigned int *vectorable = (unsigned int *)0x0;
int VectorCodeSize = 212; /* 바이트 단위의 벡터 테이블과 FIQ 핸들러 */
interrupt_state = disavle_interrupts();
enableCache(); /* 13장 MPU/MMU참고 */
flushCache(); /* 예제 12.2. 참고 */
/* 전역 데이터 블럭 락 */
wayIndex = lockDCache(globalData, sizeof(globalData));
/* 벡터 테이블과 FIQ 핸들러 락*/
wayIndex = lockICache((vectortable. vectorCodeSize);)
enable_interrupts(interrupt_state); /* 어떤 코드도 제공되지 않음 */
- 시작에서 인터럽트 disabled, 캐시 enabled
- lockDCache() : D 캐시 안에 데이터를 한 블럭 locking
- lockICache() : I 캐시 안에 데이터를 한 블럭 locking
* lock down 루틴은 캐시 설정이 안되어있는 메인메모리에 위치해야 함
캐시 안에서 락되어 있는 코드와 데이터는 캐시 설정된 메인메모리 안에 위치해야 함
캐시 안에 있는 코드와 데이터가 캐시 이외의 다른 장소에는 존재할 수 없음
=> 만약 캐시 내용이 알려져 있지 않다면 로딩 전에 캐시를 flush, writeback에 D캐시라면 clean.
- 코드와 데이터가 캐시 안에 로드되면 인러텁트를 다시 enable
> 아키텍처에 따라 캐시안에 코드를 locking하는 방법 3가지
1. way 어드레싱 기술을 사용
2. lock-bit을 사용
3. 특별한 할당 명령어와 메인메모리의 전용 블록을 읽어서 수행
> lockDCache, lockICache를 구현하기 위한 예제와 사용방법 및 관련 프로세서들
## 12.6.2. WAY index를 증가시켜 캐시 락하기
- ARM920T, ARM926EJ-S, ARM940R, ARM946E-S, ARM1022E, ARM1026EJ-S 에서는 lock down을 위해 way와 set index addressing을 사용
- 2개의 CP15:c9:c0 레지스터는 victim 카운터 리셋 레지스터들을 포함하여 하나는 D캐시를 하나는 I캐시를 제어
=> 데이터를 locking하는 way내에서 캐시 라인을 선택하는데 사용
- CP15:c7 레지스터에 쓰여진 값은 victime 리셋값을 설정 (victim 카운터가 코어에서 ways의 수 이상으로 증가했을 때 초기화 될 값)
=> 전원을 켤 때 0, 캐시의 일부가 락다운을 위해 사용되면 SW적으로 변경이 가능
- 캐시의 일부가 락다운을 위해 사용될 때, 정보를 일시 저장하기 위한 캐시라인의 수는 락다운된 캐시 라인의 수만큼 감소
(사용가능한 캐시라인의 수 = 전체 캐시라인 수 - 락다운된 캐시라인의 수)
- 레지스터를 읽으면 현재의 victim 리셋값이 return
> 두 레지스터를 읽거나 쓰기 위한 MRC, MCR명령어
> 해당 way값을 참조하여 캐시 안에 데이터를 락할 때 사용되는 CP15:c9 레지스터 포맷
: lockdown address를 read/write 할 때 MCR, MRC 명령어 안에서 사용되는 코어 레지스터 Rd의 포맷은 프로세서마다 다름
> 특별한 load 명령어의 사용
: 메인메모리의 cache-line-size의 블럭을 I캐시 안의 캐시라인에 복사
> Example 12.7. <
: 증가하는 way 어드레싱을 사용하여 lockdown을 지원하는 프로세서를 위한 lockDCache, lockICache 루틴
: 두 루틴에 대한 return value는 사용가능한 victim pointer base address.
- CACHELOCKBYWAY에서 사용되는 레지스터를 정의
(cache.h의 상수도 사용)
- 주소(adr)를 캐시라인에 정렬
- 코드를 포함하는 way의 수를 결정하기 위해 바이트로 된 코드 크기를 사용
- I캐시와 D캐시 현재 victim포인터를 CP15:c9:c0에서 읽어옴
- overflow나 로드한 데이터의 크기가 0인지를 체크
- ARM940T, ARM946E-S의 캐시안에 코드나 데이터를 락하기 위해선
캐시라인 안에 메모리블록을 락하기 전에 락비트를 먼저 설정 해야함
=> 락비스 셋, 데이터를 CP13:c9:c0에 저장
- 중복 loop에 진입하여 외부 loop에서는 way를 선택, 내부 loop에서는 way안에 있는 캐시라인을 증가
## 12.6.3. 락비트을 사용하여 캐시 락하기
* ARM926EJ-S, ARM1026EJ-S는 한 세트의 락비트를 사용
- 0-3 bit : 두 프로세서에 대해 4-way 세트 연상 캐시 안에서 각 way를 표시
=> 비트가 1이면, 해당 way는 locking되어있는 상태
=> I캐시라면 코드를 포함하고 있고, D캐시라면 데이터를 포함하고 있음
- locking이 된 way는 lock이 풀릴 때가지 캐시 라인의 복귀를 하지 않음
- locking 풀기 : L 비트 중 하나를 클리어하여 구현
=> way 각각에 대한 개별적인 lock 구현이 가능(각 way의 개별적인 locking은 시스템 코드를 보다 쉽게 lock/unlock 할 수 있게 됨)
> ARM926EJ-S, ARM1026EJ-S의 CP15:c9 명령어
> Example 12.8 <
- ARM926EJ-S, ARM1026EJ-S를 위한 lockDCache, lockICache는 동일한 입력 파라미터를 갖음
- but, 코드 크기가 way 크기의 최대값으로 제한이 되며 3번까지만 호출이 가능
- L bit (3)은 항상 캐시전용으로 프로세서 하드웨어의 제한 및 프로그램밍의 필요를 충족시키기 위한 프로시저 콜에 주어진 제한일 뿐
- 크기 파라미터가 1byte 이상이라면, locking된 way의 L비트를 return.
- lockdown을 위해 사용가능한 way가 없다면 8을 return.
## 12.6.4. 인텔 XScale SA-110에서 캐시라인 락하기
- CP15:c9의 캐시 lockdown 명령어를 사용
- CP15:c7 allocate D캐시라인 명령어 사용
- XScale은 프로세서는 라운드로빈 포인터를 가짐
=> 캐시안에 있는 각 설정값들을 캐시 안에 있는 추가의 캐시 라인이 locking 될 대마다, 순차적으로 증가
* 1 세트 안에서 32개의 캐시라인 중 28개까지 locking이 가능하며,
그 이상의 캐시의 lock을 시도해도 캐시안에서는 할당된 것 처럼 보이지만 실제로는 locking이 되어있지 않음
> XScale에서 D캐시안의 데이터 locking하는 2가지 방법
1. 메인메모리의 위치를 D캐시 쪽으로 locking
2. 캐시의 일부를 데이터 RAM으로 재설정 하기 위해 allocate 캐시 라인 명령어를 사용
=> 할당된 캐시의 부분은 초기화 되어있지 않고, 유효 데이터를 포함하기 위해 프로세서 코어로부터 쓰기를 필요로 함
> Example 12.9 <
- CACHELOCKREGION 매크로에서 사용될 레지스터를 정의. cache.h의 상수들을 사용
- 주소(adr)을 캐시라인에 정렬, 코드를 포함할 캐시라인의 수를 결정
- 프로시저가 D캐시안에 데이터를 lock하고 있다면 다음의 몇몇 라인들이 write-buffer에 놓여 D캐시를 unlocking 함
(D 캐시 안에 데이터를 locking할 경우에는 D 캐시라인을 locking 하기 전 unlocking 명령어를 사용해야 함)
=> CP15:c9:c20 레지스터에 1로 비트를 설정
- 코드는 locking 중인 코드나 데이터를 가진 캐시를 채우기 위해 loop를 돌며 아래를 수행
- 프로시저가 I 캐시 안에서 코드를 locking 중이면, lock I 캐시 라인 명령어를 실행
- 외부 메모리로부터 데이터를 locking 중이면, 새로운 캐시라인을 D 캐시 쪽으로 clean/flush하며 load
- 데이터 RAM을 만들었다면 D캐시를 할당하고 28세트 이상 lock을 시도한 결과 발생할 수 있는 에러에 대해 보호할 write-buffer를 끄집어 내기
- STRD 명령어로 캐시라인을 0으로 초기화
- D 캐시데이터가 locking 중이라면 매크로는 캐시로드 CP15 레지스터에서 lock-bit을 0으로 클리어하여 종료
### 12.7. Caches and Software Performance
: 캐시 아키텍처를 이용하는 코드 작성 시 몇가지 규칙들
- 메모리 시스템 안에 있는 대부분의 region 들은 캐시 아키텍처를 최대한 활용 할 수 있도록 cache와 write-buffer를 모두 enable.
=> 평균 메모리 액세스 시간을 줄임
- 메모리 매핑된 주변장치들은 cache나 write-buffer를 사용하도록 설정된 경우 종종 오작동을 일으킴
=> 즉, 주변장치들은 메모리가 cache나 write-buffer를 사용하지 안도록 하는 것이 좋음
(캐시안에 저장되어 있는 오래된 데이터를 읽는 대신 매번 메모리 액세스를 통해 주변장치를 읽음)
- 캐시라인을 채우려면 메인메모리에서 새로운 데이터를 읽는 비용이 발생함
=> 자주 액세스하는 데이터는 메모리 안에 연속적으로 저장하는게 좋음. 보통의 루틴들이 액세스하는 데이터들을 메인메모리 안의 가가이에 함께 위치.
(locality를 통해 cache hit-rate를 높여줄 수 있음)
- writing/processing/reading이 cache-line-size의 블록으로 이루어지도록 데이터를 구조화 하기
=> cache-line-size의 블럭은 메인메모리의 낮은 주소가 캐시라인의 시작주소와 일치시켜야 함
- 캐시를 사용시 linked-list는 프로그램의 성능을 저하시킬 수 있음
=> 리스트를 찾는 작업에서 많은 cache-miss가 발생할 수 있음
'System > Embedded' 카테고리의 다른 글
[ARM] MPU (0) | 2015.11.17 |
---|---|
[ARM] 캐시 (0) | 2015.11.12 |
[ARM] 펌웨어 (0) | 2015.11.12 |
[ARM] 익셉션과 인터럽트 처리 2 (0) | 2015.11.10 |
[ARM] 익셉션과 인터럽트 처리 (0) | 2015.11.10 |