前言
最近在研究Linux IO相關(guān)的知識,突然想起來Binder機制可以傳遞fd,但是沒有仔細(xì)考慮過下面這個問題。
Client端fd和Server端fd,內(nèi)核中指向兩個的file結(jié)構(gòu)體還是指向同一個file結(jié)構(gòu)體?
一、兩者的區(qū)別
1.1 有人可能會問:兩者有什么區(qū)別?
區(qū)別大了,如果指向同一個file結(jié)構(gòu)體,就意味的兩個進(jìn)程共享同一個f_pos讀寫指針。
1.2 f_pos讀寫指針是什么?
f_pos讀寫指針是用于記錄當(dāng)前文件讀寫的位置
舉個例子:
假設(shè)一個文件1.txt的內(nèi)容是"helloworld"。
進(jìn)程A和進(jìn)程B對應(yīng)的fd指向同一個file,就會共享f_pos。
進(jìn)程A先讀5個字節(jié),就會讀到"hello"
進(jìn)程B再讀5個字節(jié),就會讀到"world"
二、Binder驅(qū)動源碼
以下代碼運行在Client端的線程,并且都在內(nèi)核中
2.1 binder_translate_fd
static int binder_translate_fd(int fd,
struct binder_transaction *t,
struct binder_thread *thread,
struct binder_transaction *in_reply_to)
{
//獲取Server端的binder_proc
struct binder_proc *target_proc = t->to_proc;
int target_fd;
struct file *file;
//獲得Client端中fd對應(yīng)的file結(jié)構(gòu)體
file = fget(fd);
//獲取Server端的一個空閑的target_fd
target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
//將target_fd和file綁定
task_fd_install(target_proc, target_fd, file);//跳轉(zhuǎn)2.2
//返回server端的fd,也就是target_fd
return target_fd;
}
2.2 task_fd_install
static void task_fd_install(
struct binder_proc *proc, unsigned int fd, struct file *file)
{
mutex_lock(&proc->files_lock);
if (proc->files)
__fd_install(proc->files, fd, file);//跳轉(zhuǎn)2.3
mutex_unlock(&proc->files_lock);
}
2.3 __fd_install
void __fd_install(struct files_struct *files, unsigned int fd,
struct file *file)
{
//每一個進(jìn)程關(guān)聯(lián)著一個files_struct結(jié)構(gòu)體
//files_struct結(jié)構(gòu)體中有一個fdtable結(jié)構(gòu)體
struct fdtable *fdt;
//獲取files_struct中的fdtable
fdt = rcu_dereference_sched(files->fdt);
//fdtable保存了一個file指針數(shù)組fd
//將fd[fd]指向file結(jié)構(gòu)體,這兩個fd不同,前者表示指針數(shù)組,后者表示形參中int fd
rcu_assign_pointer(fdt->fd[fd], file);
}
整個關(guān)系如下圖:
2.4 小結(jié)
從源碼來看binder傳輸fd,指向同一個file結(jié)構(gòu)體,共享同一個f_pos讀寫指針
三、寫的Demo
3.1 Client端
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private IBinder mSendFd;
private TextView mTxtSend;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTxtSend = findViewById(R.id.txt_send);
mTxtSend.setOnClickListener(this);
ping();
writeFile();
}
//綁定Binder
private void ping() {
Intent intent = new Intent(this, RemoteService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mSendFd = service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, Context.BIND_AUTO_CREATE);
}
//新建一個文件
private void writeFile() {
try {
String content = "helloworld";
File file = new File(this.getFilesDir().getPath() + "/" + "1.txt");
if (!file.exists()) {
file.createNewFile();
}
FileWriter fileWriter = new FileWriter(file.getAbsoluteFile());
BufferedWriter bw = new BufferedWriter(fileWriter);
bw.write(content);
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onClick(View v) {
try {
FileDescriptor fd = readFile();
Parcel data = Parcel.obtain();
data.writeFileDescriptor(fd);
mSendFd.transact(1, data, null, 0);
} catch (Exception e) {
}
}
//讀取5個字符
private FileDescriptor readFile() {
try {
FileDescriptor fd = Os.open(this.getFilesDir().getPath() + "/" + "1.txt", OsConstants.O_RDONLY, 0600);
byte[] buf = new byte[5];
Os.read(fd, buf, 0, buf.length);
Log.v("KobeWang3", "This is Client : " + new String(buf));
return fd;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
3.2 Server端
public class RemoteService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new SendBinder();
}
public class SendBinder extends Binder {
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
if (code == 1) {
try {
FileDescriptor fd = data.readFileDescriptor().getFileDescriptor();
FileReader fileReader = new FileReader(fd);
BufferedReader bf = new BufferedReader(fileReader);
String str = bf.readLine();
Log.v("KobeWang3", "This is Server : " + str);
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
return super.onTransact(code, data, reply, flags);
}
}
}
別忘了讓RemoteService運行在其他進(jìn)程
<service
android:name=".RemoteService"
android:exported="true"
android:process=":remote">
</service>
3.3 運行結(jié)果
果然結(jié)果和我們分析的一致
11747 11747 V KobeWang3: This is Client : hello
11810 11830 V KobeWang3: This is Server : world
四、ParcelFileDescriptor
ParcelFileDescriptor是android提供的,繼承于Parcelable,可以在AIDL中直接使用。
4.1 用法
用java層File對象創(chuàng)建ParcelFileDescriptor
public static ParcelFileDescriptor open(File file, int mode) throws FileNotFoundException {
final FileDescriptor fd = openInternal(file, mode);
if (fd == null) return null;
return new ParcelFileDescriptor(fd);
}
private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException {
final int flags = FileUtils.translateModePfdToPosix(mode) | ifAtLeastQ(O_CLOEXEC);
int realMode = S_IRWXU | S_IRWXG;
if ((mode & MODE_WORLD_READABLE) != 0) realMode |= S_IROTH;
if ((mode & MODE_WORLD_WRITEABLE) != 0) realMode |= S_IWOTH;
final String path = file.getPath();
try {
return Os.open(path, flags, realMode);//重新open一次。
} catch (ErrnoException e) {
throw new FileNotFoundException(e.getMessage());
}
}
4.2 注意點
file1:java層File對象對應(yīng)的fd1指向內(nèi)核空間的file結(jié)構(gòu)體
file2:ParcelFileDescriptor會根據(jù)path重新open,新建一個fd2指向內(nèi)核空間新建的file結(jié)構(gòu)體
雖然file1和file2指向同一個實體文件,但是兩者的讀寫指針是獨立的。
經(jīng)過Binder通信傳遞ParcelFileDescriptor對象。Server端拿到的fd1指向的是file2。
假如Client端用java層File對象讀文件,Server端拿到的ParcelFileDescriptor對應(yīng)的fd1讀寫文件,兩者并不會有任何影響。
五、為什么要學(xué)Linux Kernel
作為Java程序員出身我,其實對Linux Kernel并不熟悉,一年前,我開始努力嘗試學(xué)習(xí)Linux Kernel,發(fā)現(xiàn)自己對很多上層的細(xì)節(jié),有了更加深入的理解,我相信我繼續(xù)努力學(xué)下去。