目次: Linux
RISC CPUにはワード幅での読み書きしかできないアーキテクチャがありますが、より狭いハーフワードやバイトへのアクセスってどうしているのでしょう?
単純に考えると、ひとまず近しいアドレスからワード幅で読み出して、しかるべきシフト演算を行うことで、目的のハーフワードやバイトデータを得ていそうです。
例えば、ワード幅64bits、読みたいデータ幅16bits、アクセス先のアドレスが0x12として考えてみます。
まず、データバスにはアドレス0x12ではアクセスできませんので、ひとまず0x12を超えない最大の8の倍数(64bits = 8bytes)であるアドレス0x10から64bitsを読み出します。
このときバスから読み出したデータが0x1234_5678_0246_8aceだとして、バスから読み出したデータを読みたいデータ幅(= 16bits)ごとに分割し、符号ビットから近い順から並べると、
0x1234:
0x5678:
0x0246:
0x8ace:
となります。
リトルエンディアンシステムの場合、データの上位から、アドレス+6、アドレス+4、アドレス+2、アドレスそのもの、に対応しますので、
0x1234: アドレス+6 = 0x16
0x5678: アドレス+4 = 0x14
0x0246: アドレス+2 = 0x12
0x8ace: アドレスそのもの = 0x10
と対応します。
従って目的のアドレス0x12にあるデータは0x0246であることがわかり、バスから読み出したデータをシフトすべき量は16bitsであることがわかります。
同様にアドレス0x14ならばデータは0x5678となり、シフトすべき量は32bitsです。
このような処理をいちいち考えていると面倒で死にそうなので、コードで書いてみることにしました。
public static long ADDR_MASK_64 = ~0x7L;
public static long ADDR_MASK_32 = ~0x3L;
public static long ADDR_MASK_16 = ~0x1L;
public static long ADDR_MASK_8 = ~0x0L;
/**
* @param dataLenデータ幅
* @returnアドレスマスク
*/
public long getAddressMask(int dataLen) {
switch (dataLen) {
case 64:
return ADDR_MASK_64;
case 32:
return ADDR_MASK_32;
case 16:
return ADDR_MASK_16;
case 8:
return ADDR_MASK_8;
default:
throw new IllegalArgumentException("Data length" +
String.format("(0x%08x) is not supported.", dataLen));
}
}
/*
* @param addr データのアドレス
* @param data バスから読んだデータ
* @param busLen データバス幅
* @param dataLen データ幅
* @return addrにあるデータ
*/
public long readMasked(long addr, long data, int busLen, int dataLen) {
long busMask = getAddressMask(busLen);
long dataMask = getAddressMask(dataLen);
int sh = (int)(addr & ~busMask & dataMask) * 8;
return data >> sh;
}
ふっざけんなー!意味がわからんわー!!と叫んでいる半年後の自分が見えたので、併せて解説も書いておきます。
バスから読み出したデータをシフトする量を求める部分がaddr & ~busMask & dataMask * 8の部分です。
まずaddr & ~busMaskですが、バス幅で割った余りのアドレスを求めています。
例えば、幅が64bitsでアドレスが0x12ならば、8で割った余りのアドレス0x02を求めています。
次にaddr & ~busMask & dataMaskですが、データ幅境界にアドレスを揃えています。この意味と必要性の議論は後述します。
例えばデータ幅が16bitsならば、アドレスを2の倍数にします。アドレス0x01なら0x00、アドレス0x02なら0x02、アドレス0x03なら0x02です。
残りの処理はアドレスに8を掛けて右ビットシフトしています。関数の返値は上位に余計なデータが残っていますので、返り値を受け取った人は、余計な上位のデータを捨てる必要があります。
呼び出す側のコードは、こんな感じです。
//データバス幅
public static int BITS_DATA_BUS = 64;
public byte read8(long addr) {
return (byte)readMasked(addr, readWord(addr), BITS_DATA_BUS, 8);
}
public short read16(long addr) {
return (short)readMasked(addr, readWord(addr), BITS_DATA_BUS, 16);
}
public int read32(long addr) {
return (int)readMasked(addr, readWord(addr), BITS_DATA_BUS, 32);
}
他のアドレスが渡される場合も同様です。
他のバス幅、他のビット幅でも同じ考え方で処理可能です。
データ幅境界以外からデータを読んで良い、つまり0x13から読んだときに0x0246ではなくて、0x7802を返せるシステムならば、& dataMaskは不要です。
この方が便利ですが良いことばかりでもなく、バス幅をまたぐ際の読み出し、例えば0x17から16bits読む際の処理が必要になります。
しかし前述のコードではバス幅の境界をまたぐ読み出し方に対応できませんから、データ幅境界以外からデータを読めないようにして、異常動作しないように防いでいます。
この記事にコメントする
| < | 2014 | > | ||||
| << | < | 06 | > | >> | ||
| 日 | 月 | 火 | 水 | 木 | 金 | 土 |
| 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 | - | - | - | - | - |
26年2月23日
21年12月28日
26年2月15日
26年2月11日
23年4月10日
23年5月15日
26年2月8日
26年2月9日
23年6月1日
24年2月2日
26年2月1日
21年5月7日
26年1月19日
26年1月29日
26年1月23日
26年1月21日
25年12月26日
25年12月22日
08年3月25日
25年12月10日
wiki
Linux JM
Java API
2002年
2003年
2004年
2005年
2006年
2007年
2008年
2009年
2010年
2011年
2012年
2013年
2014年
2015年
2016年
2017年
2018年
2019年
2020年
2021年
2022年
2023年
2024年
2025年
2026年
過去日記について
アクセス統計
サーバ一覧
サイトの情報合計:
本日: