ChildProcessLauncher.java revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
1// Copyright 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.content.browser; 6 7import android.content.Context; 8import android.util.Log; 9import android.util.SparseIntArray; 10import android.view.Surface; 11 12import java.util.ArrayList; 13import java.util.Map; 14import java.util.concurrent.ConcurrentHashMap; 15 16import org.chromium.base.CalledByNative; 17import org.chromium.base.JNINamespace; 18import org.chromium.base.SysUtils; 19import org.chromium.base.ThreadUtils; 20import org.chromium.content.app.ChildProcessService; 21import org.chromium.content.app.Linker; 22import org.chromium.content.app.LinkerParams; 23import org.chromium.content.app.PrivilegedProcessService; 24import org.chromium.content.app.SandboxedProcessService; 25import org.chromium.content.common.IChildProcessCallback; 26import org.chromium.content.common.IChildProcessService; 27 28/** 29 * This class provides the method to start/stop ChildProcess called by native. 30 */ 31@JNINamespace("content") 32public class ChildProcessLauncher { 33 private static String TAG = "ChildProcessLauncher"; 34 35 private static final int CALLBACK_FOR_UNKNOWN_PROCESS = 0; 36 private static final int CALLBACK_FOR_GPU_PROCESS = 1; 37 private static final int CALLBACK_FOR_RENDERER_PROCESS = 2; 38 39 private static final String SWITCH_PROCESS_TYPE = "type"; 40 private static final String SWITCH_PPAPI_BROKER_PROCESS = "ppapi-broker"; 41 private static final String SWITCH_RENDERER_PROCESS = "renderer"; 42 private static final String SWITCH_GPU_PROCESS = "gpu-process"; 43 44 // The upper limit on the number of simultaneous sandboxed and privileged child service process 45 // instances supported. Each limit must not exceed total number of SandboxedProcessServiceX 46 // classes and PrivilegedProcessServiceX classes declared in this package and defined as 47 // services in the embedding application's manifest file. 48 // (See {@link ChildProcessService} for more details on defining the services.) 49 /* package */ static final int MAX_REGISTERED_SANDBOXED_SERVICES = 13; 50 /* package */ static final int MAX_REGISTERED_PRIVILEGED_SERVICES = 3; 51 52 private static class ChildConnectionAllocator { 53 // Connections to services. Indices of the array correspond to the service numbers. 54 private ChildProcessConnection[] mChildProcessConnections; 55 56 // The list of free (not bound) service indices. When looking for a free service, the first 57 // index in that list should be used. When a service is unbound, its index is added to the 58 // end of the list. This is so that we avoid immediately reusing the freed service (see 59 // http://crbug.com/164069): the framework might keep a service process alive when it's been 60 // unbound for a short time. If a new connection to the same service is bound at that point, 61 // the process is reused and bad things happen (mostly static variables are set when we 62 // don't expect them to). 63 // SHOULD BE ACCESSED WITH mConnectionLock. 64 private ArrayList<Integer> mFreeConnectionIndices; 65 private final Object mConnectionLock = new Object(); 66 67 private Class<? extends ChildProcessService> mChildClass; 68 private final boolean mInSandbox; 69 70 public ChildConnectionAllocator(boolean inSandbox) { 71 int numChildServices = inSandbox ? 72 MAX_REGISTERED_SANDBOXED_SERVICES : MAX_REGISTERED_PRIVILEGED_SERVICES; 73 mChildProcessConnections = new ChildProcessConnection[numChildServices]; 74 mFreeConnectionIndices = new ArrayList<Integer>(numChildServices); 75 for (int i = 0; i < numChildServices; i++) { 76 mFreeConnectionIndices.add(i); 77 } 78 setServiceClass(inSandbox ? 79 SandboxedProcessService.class : PrivilegedProcessService.class); 80 mInSandbox = inSandbox; 81 } 82 83 public void setServiceClass(Class<? extends ChildProcessService> childClass) { 84 mChildClass = childClass; 85 } 86 87 public ChildProcessConnection allocate( 88 Context context, ChildProcessConnection.DeathCallback deathCallback, 89 LinkerParams linkerParams) { 90 synchronized(mConnectionLock) { 91 if (mFreeConnectionIndices.isEmpty()) { 92 Log.w(TAG, "Ran out of service." ); 93 return null; 94 } 95 int slot = mFreeConnectionIndices.remove(0); 96 assert mChildProcessConnections[slot] == null; 97 mChildProcessConnections[slot] = new ChildProcessConnection(context, slot, 98 mInSandbox, deathCallback, mChildClass, linkerParams); 99 return mChildProcessConnections[slot]; 100 } 101 } 102 103 public void free(ChildProcessConnection connection) { 104 synchronized(mConnectionLock) { 105 int slot = connection.getServiceNumber(); 106 if (mChildProcessConnections[slot] != connection) { 107 int occupier = mChildProcessConnections[slot] == null ? 108 -1 : mChildProcessConnections[slot].getServiceNumber(); 109 Log.e(TAG, "Unable to find connection to free in slot: " + slot + 110 " already occupied by service: " + occupier); 111 assert false; 112 } else { 113 mChildProcessConnections[slot] = null; 114 assert !mFreeConnectionIndices.contains(slot); 115 mFreeConnectionIndices.add(slot); 116 } 117 } 118 } 119 } 120 121 // Service class for child process. As the default value it uses SandboxedProcessService0 and 122 // PrivilegedProcessService0. 123 private static final ChildConnectionAllocator sSandboxedChildConnectionAllocator = 124 new ChildConnectionAllocator(true); 125 private static final ChildConnectionAllocator sPrivilegedChildConnectionAllocator = 126 new ChildConnectionAllocator(false); 127 128 private static boolean sConnectionAllocated = false; 129 130 // Sets service class for sandboxed service and privileged service. 131 public static void setChildProcessClass( 132 Class<? extends SandboxedProcessService> sandboxedServiceClass, 133 Class<? extends PrivilegedProcessService> privilegedServiceClass) { 134 // We should guarantee this is called before allocating connection. 135 assert !sConnectionAllocated; 136 sSandboxedChildConnectionAllocator.setServiceClass(sandboxedServiceClass); 137 sPrivilegedChildConnectionAllocator.setServiceClass(privilegedServiceClass); 138 } 139 140 private static ChildConnectionAllocator getConnectionAllocator(boolean inSandbox) { 141 return inSandbox ? 142 sSandboxedChildConnectionAllocator : sPrivilegedChildConnectionAllocator; 143 } 144 145 private static ChildProcessConnection allocateConnection(Context context, 146 boolean inSandbox, LinkerParams linkerParams) { 147 ChildProcessConnection.DeathCallback deathCallback = 148 new ChildProcessConnection.DeathCallback() { 149 @Override 150 public void onChildProcessDied(int pid) { 151 stop(pid); 152 } 153 }; 154 sConnectionAllocated = true; 155 return getConnectionAllocator(inSandbox).allocate(context, deathCallback, linkerParams); 156 } 157 158 private static boolean sLinkerInitialized = false; 159 private static long sLinkerLoadAddress = 0; 160 161 private static LinkerParams getLinkerParamsForNewConnection() { 162 if (!sLinkerInitialized) { 163 if (Linker.isUsed()) { 164 sLinkerLoadAddress = Linker.getBaseLoadAddress(); 165 if (sLinkerLoadAddress == 0) { 166 Log.i(TAG, "Shared RELRO support disabled!"); 167 } 168 } 169 sLinkerInitialized = true; 170 } 171 172 if (sLinkerLoadAddress == 0) 173 return null; 174 175 // Always wait for the shared RELROs in service processes. 176 final boolean waitForSharedRelros = true; 177 return new LinkerParams(sLinkerLoadAddress, 178 waitForSharedRelros, 179 Linker.getTestRunnerClassName()); 180 } 181 182 private static ChildProcessConnection allocateBoundConnection(Context context, 183 String[] commandLine, boolean inSandbox) { 184 LinkerParams linkerParams = getLinkerParamsForNewConnection(); 185 ChildProcessConnection connection = allocateConnection(context, inSandbox, linkerParams); 186 if (connection != null) { 187 connection.start(commandLine); 188 } 189 return connection; 190 } 191 192 private static void freeConnection(ChildProcessConnection connection) { 193 if (connection == null) { 194 return; 195 } 196 getConnectionAllocator(connection.isInSandbox()).free(connection); 197 return; 198 } 199 200 // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle. 201 private static final int NULL_PROCESS_HANDLE = 0; 202 203 // Map from pid to ChildService connection. 204 private static Map<Integer, ChildProcessConnection> sServiceMap = 205 new ConcurrentHashMap<Integer, ChildProcessConnection>(); 206 207 // A pre-allocated and pre-bound connection ready for connection setup, or null. 208 private static ChildProcessConnection sSpareSandboxedConnection = null; 209 210 /** 211 * Manages oom bindings used to bound child services. "Oom binding" is a binding that raises the 212 * process oom priority so that it shouldn't be killed by the OS out-of-memory killer under 213 * normal conditions (it can still be killed under drastic memory pressure). 214 * 215 * This class serves a proxy between external calls that manipulate the bindings and the 216 * connections, allowing to enforce policies such as delayed removal of the bindings. 217 */ 218 static class BindingManager { 219 // Delay of 1 second used when removing the initial oom binding of a process. 220 private static final long REMOVE_INITIAL_BINDING_DELAY_MILLIS = 1 * 1000; 221 222 // Delay of 5 second used when removing temporary strong binding of a process (only on 223 // non-low-memory devices). 224 private static final long DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS = 5 * 1000; 225 226 // Map from pid to the count of oom bindings bound for the service. Should be accessed with 227 // mCountLock. 228 private final SparseIntArray mOomBindingCount = new SparseIntArray(); 229 230 // Pid of the renderer that was most recently oom bound. This is used on low-memory devices 231 // to drop oom bindings of a process when another one acquires them, making sure that only 232 // one renderer process at a time is oom bound. Should be accessed with mCountLock. 233 private int mLastOomPid = -1; 234 235 // Should be acquired before binding or unbinding the connections and modifying state 236 // variables: mOomBindingCount and mLastOomPid. 237 private final Object mCountLock = new Object(); 238 239 /** 240 * Registers an oom binding bound for a child process. Should be called with mCountLock. 241 * @param pid handle of the process. 242 */ 243 private void incrementOomCount(int pid) { 244 mOomBindingCount.put(pid, mOomBindingCount.get(pid) + 1); 245 mLastOomPid = pid; 246 } 247 248 /** 249 * Registers an oom binding unbound for a child process. Should be called with mCountLock. 250 * @param pid handle of the process. 251 */ 252 private void decrementOomCount(int pid) { 253 int count = mOomBindingCount.get(pid, -1); 254 assert count > 0; 255 count--; 256 if (count > 0) { 257 mOomBindingCount.put(pid, count); 258 } else { 259 mOomBindingCount.delete(pid); 260 } 261 } 262 263 /** 264 * Drops all oom bindings for the given renderer. 265 * @param pid handle of the process. 266 */ 267 private void dropOomBindings(int pid) { 268 ChildProcessConnection connection = sServiceMap.get(pid); 269 if (connection == null) { 270 LogPidWarning(pid, "Tried to drop oom bindings for a non-existent connection"); 271 return; 272 } 273 synchronized (mCountLock) { 274 connection.dropOomBindings(); 275 mOomBindingCount.delete(pid); 276 } 277 } 278 279 /** 280 * Registers a freshly started child process. On low-memory devices this will also drop the 281 * oom bindings of the last process that was oom-bound. We can do that, because every time a 282 * connection is created on the low-end, it is used in foreground (no prerendering, no 283 * loading of tabs opened in background). 284 * @param pid handle of the process. 285 */ 286 void addNewConnection(int pid) { 287 synchronized (mCountLock) { 288 if (SysUtils.isLowEndDevice() && mLastOomPid >= 0) { 289 dropOomBindings(mLastOomPid); 290 } 291 // This will reset the previous entry for the pid in the unlikely event of the OS 292 // reusing renderer pids. 293 mOomBindingCount.put(pid, 0); 294 // Every new connection is bound with initial oom binding. 295 incrementOomCount(pid); 296 } 297 } 298 299 /** 300 * Remove the initial binding of the child process. Child processes are bound with initial 301 * binding to protect them from getting killed before they are put to use. This method 302 * allows to remove the binding once it is no longer needed. The binding is removed after a 303 * fixed delay period so that the renderer will not be killed immediately after the call. 304 */ 305 void removeInitialBinding(final int pid) { 306 final ChildProcessConnection connection = sServiceMap.get(pid); 307 if (connection == null) { 308 LogPidWarning(pid, "Tried to remove a binding for a non-existent connection"); 309 return; 310 } 311 if (!connection.isInitialBindingBound()) return; 312 ThreadUtils.postOnUiThreadDelayed(new Runnable() { 313 @Override 314 public void run() { 315 synchronized (mCountLock) { 316 if (connection.isInitialBindingBound()) { 317 decrementOomCount(pid); 318 connection.removeInitialBinding(); 319 } 320 } 321 } 322 }, REMOVE_INITIAL_BINDING_DELAY_MILLIS); 323 } 324 325 /** 326 * Bind a child process as a high priority process so that it has the same priority as the 327 * main process. This can be used for the foreground renderer process to distinguish it from 328 * the background renderer process. 329 * @param pid The process handle of the service connection. 330 */ 331 void bindAsHighPriority(final int pid) { 332 ChildProcessConnection connection = sServiceMap.get(pid); 333 if (connection == null) { 334 LogPidWarning(pid, "Tried to bind a non-existent connection"); 335 return; 336 } 337 synchronized (mCountLock) { 338 connection.attachAsActive(); 339 incrementOomCount(pid); 340 } 341 } 342 343 /** 344 * Unbind a high priority process which was previous bound with bindAsHighPriority. 345 * @param pid The process handle of the service. 346 */ 347 void unbindAsHighPriority(final int pid) { 348 final ChildProcessConnection connection = sServiceMap.get(pid); 349 if (connection == null) { 350 LogPidWarning(pid, "Tried to unbind non-existent connection"); 351 return; 352 } 353 if (!connection.isStrongBindingBound()) return; 354 355 // This runnable performs the actual unbinding. It will be executed synchronously when 356 // on low-end devices and posted with a delay otherwise. 357 Runnable doUnbind = new Runnable() { 358 @Override 359 public void run() { 360 synchronized (mCountLock) { 361 if (connection.isStrongBindingBound()) { 362 decrementOomCount(pid); 363 connection.detachAsActive(); 364 } 365 } 366 } 367 }; 368 369 if (SysUtils.isLowEndDevice()) { 370 doUnbind.run(); 371 } else { 372 ThreadUtils.postOnUiThreadDelayed(doUnbind, DETACH_AS_ACTIVE_HIGH_END_DELAY_MILLIS); 373 } 374 } 375 376 /** 377 * @return True iff the given service process is protected from the out-of-memory killing, 378 * or it was protected when it died (either crashed or was closed). This can be used to 379 * decide if a disconnection of a renderer was a crash or a probable out-of-memory kill. In 380 * the unlikely event of the OS reusing renderer pid, the call will refer to the most recent 381 * renderer of the given pid. The binding count is being reset in addNewConnection(). 382 */ 383 boolean isOomProtected(int pid) { 384 synchronized (mCountLock) { 385 return mOomBindingCount.get(pid) > 0; 386 } 387 } 388 } 389 390 private static BindingManager sBindingManager = new BindingManager(); 391 392 static BindingManager getBindingManager() { 393 return sBindingManager; 394 } 395 396 /** 397 * Returns the child process service interface for the given pid. This may be called on 398 * any thread, but the caller must assume that the service can disconnect at any time. All 399 * service calls should catch and handle android.os.RemoteException. 400 * 401 * @param pid The pid (process handle) of the service obtained from {@link #start}. 402 * @return The IChildProcessService or null if the service no longer exists. 403 */ 404 public static IChildProcessService getChildService(int pid) { 405 ChildProcessConnection connection = sServiceMap.get(pid); 406 if (connection != null) { 407 return connection.getService(); 408 } 409 return null; 410 } 411 412 /** 413 * Should be called early in startup so the work needed to spawn the child process can be done 414 * in parallel to other startup work. Must not be called on the UI thread. Spare connection is 415 * created in sandboxed child process. 416 * @param context the application context used for the connection. 417 */ 418 public static void warmUp(Context context) { 419 synchronized (ChildProcessLauncher.class) { 420 assert !ThreadUtils.runningOnUiThread(); 421 if (sSpareSandboxedConnection == null) { 422 sSpareSandboxedConnection = allocateBoundConnection(context, null, true); 423 } 424 } 425 } 426 427 private static String getSwitchValue(final String[] commandLine, String switchKey) { 428 if (commandLine == null || switchKey == null) { 429 return null; 430 } 431 // This format should be matched with the one defined in command_line.h. 432 final String switchKeyPrefix = "--" + switchKey + "="; 433 for (String command : commandLine) { 434 if (command != null && command.startsWith(switchKeyPrefix)) { 435 return command.substring(switchKeyPrefix.length()); 436 } 437 } 438 return null; 439 } 440 441 /** 442 * Spawns and connects to a child process. May be called on any thread. It will not block, but 443 * will instead callback to {@link #nativeOnChildProcessStarted} when the connection is 444 * established. Note this callback will not necessarily be from the same thread (currently it 445 * always comes from the main thread). 446 * 447 * @param context Context used to obtain the application context. 448 * @param commandLine The child process command line argv. 449 * @param file_ids The ID that should be used when mapping files in the created process. 450 * @param file_fds The file descriptors that should be mapped in the created process. 451 * @param file_auto_close Whether the file descriptors should be closed once they were passed to 452 * the created process. 453 * @param clientContext Arbitrary parameter used by the client to distinguish this connection. 454 */ 455 @CalledByNative 456 static void start( 457 Context context, 458 final String[] commandLine, 459 int[] fileIds, 460 int[] fileFds, 461 boolean[] fileAutoClose, 462 final int clientContext) { 463 assert fileIds.length == fileFds.length && fileFds.length == fileAutoClose.length; 464 FileDescriptorInfo[] filesToBeMapped = new FileDescriptorInfo[fileFds.length]; 465 for (int i = 0; i < fileFds.length; i++) { 466 filesToBeMapped[i] = 467 new FileDescriptorInfo(fileIds[i], fileFds[i], fileAutoClose[i]); 468 } 469 assert clientContext != 0; 470 471 int callbackType = CALLBACK_FOR_UNKNOWN_PROCESS; 472 boolean inSandbox = true; 473 String processType = getSwitchValue(commandLine, SWITCH_PROCESS_TYPE); 474 if (SWITCH_RENDERER_PROCESS.equals(processType)) { 475 callbackType = CALLBACK_FOR_RENDERER_PROCESS; 476 } else if (SWITCH_GPU_PROCESS.equals(processType)) { 477 callbackType = CALLBACK_FOR_GPU_PROCESS; 478 } else if (SWITCH_PPAPI_BROKER_PROCESS.equals(processType)) { 479 inSandbox = false; 480 } 481 482 ChildProcessConnection allocatedConnection = null; 483 synchronized (ChildProcessLauncher.class) { 484 if (inSandbox) { 485 allocatedConnection = sSpareSandboxedConnection; 486 sSpareSandboxedConnection = null; 487 } 488 } 489 if (allocatedConnection == null) { 490 allocatedConnection = allocateBoundConnection(context, commandLine, inSandbox); 491 if (allocatedConnection == null) { 492 // Notify the native code so it can free the heap allocated callback. 493 nativeOnChildProcessStarted(clientContext, 0); 494 return; 495 } 496 } 497 final ChildProcessConnection connection = allocatedConnection; 498 Log.d(TAG, "Setting up connection to process: slot=" + connection.getServiceNumber()); 499 500 ChildProcessConnection.ConnectionCallback connectionCallback = 501 new ChildProcessConnection.ConnectionCallback() { 502 public void onConnected(int pid) { 503 Log.d(TAG, "on connect callback, pid=" + pid + " context=" + clientContext); 504 if (pid != NULL_PROCESS_HANDLE) { 505 sBindingManager.addNewConnection(pid); 506 sServiceMap.put(pid, connection); 507 } else { 508 freeConnection(connection); 509 } 510 nativeOnChildProcessStarted(clientContext, pid); 511 } 512 }; 513 514 // TODO(sievers): Revisit this as it doesn't correctly handle the utility process 515 // assert callbackType != CALLBACK_FOR_UNKNOWN_PROCESS; 516 517 connection.setupConnection(commandLine, 518 filesToBeMapped, 519 createCallback(callbackType), 520 connectionCallback, 521 Linker.getSharedRelros()); 522 } 523 524 /** 525 * Terminates a child process. This may be called from any thread. 526 * 527 * @param pid The pid (process handle) of the service connection obtained from {@link #start}. 528 */ 529 @CalledByNative 530 static void stop(int pid) { 531 Log.d(TAG, "stopping child connection: pid=" + pid); 532 ChildProcessConnection connection = sServiceMap.remove(pid); 533 if (connection == null) { 534 LogPidWarning(pid, "Tried to stop non-existent connection"); 535 return; 536 } 537 connection.stop(); 538 freeConnection(connection); 539 } 540 541 /** 542 * This implementation is used to receive callbacks from the remote service. 543 */ 544 private static IChildProcessCallback createCallback(final int callbackType) { 545 return new IChildProcessCallback.Stub() { 546 /** 547 * This is called by the remote service regularly to tell us about new values. Note that 548 * IPC calls are dispatched through a thread pool running in each process, so the code 549 * executing here will NOT be running in our main thread -- so, to update the UI, we 550 * need to use a Handler. 551 */ 552 @Override 553 public void establishSurfacePeer( 554 int pid, Surface surface, int primaryID, int secondaryID) { 555 // Do not allow a malicious renderer to connect to a producer. This is only used 556 // from stream textures managed by the GPU process. 557 if (callbackType != CALLBACK_FOR_GPU_PROCESS) { 558 Log.e(TAG, "Illegal callback for non-GPU process."); 559 return; 560 } 561 562 nativeEstablishSurfacePeer(pid, surface, primaryID, secondaryID); 563 } 564 565 @Override 566 public Surface getViewSurface(int surfaceId) { 567 // Do not allow a malicious renderer to get to our view surface. 568 if (callbackType != CALLBACK_FOR_GPU_PROCESS) { 569 Log.e(TAG, "Illegal callback for non-GPU process."); 570 return null; 571 } 572 573 return nativeGetViewSurface(surfaceId); 574 } 575 }; 576 }; 577 578 private static void LogPidWarning(int pid, String message) { 579 // This class is effectively a no-op in single process mode, so don't log warnings there. 580 if (pid > 0 && !nativeIsSingleProcess()) { 581 Log.w(TAG, message + ", pid=" + pid); 582 } 583 } 584 585 private static native void nativeOnChildProcessStarted(int clientContext, int pid); 586 private static native Surface nativeGetViewSurface(int surfaceId); 587 private static native void nativeEstablishSurfacePeer( 588 int pid, Surface surface, int primaryID, int secondaryID); 589 private static native boolean nativeIsSingleProcess(); 590} 591