Android system phone startup process

2019 Unicorn Enterprise Heavy Gold Recruitment Python Engineer Standard >>> hot3.png

Part III: Android Launch

The Android startup process starts with the process init, so it is the ancestor process of all subsequent processes.

One,Init process

The source code is located in the system/core/init directory. Mainly done the following things:

1. Reset the processing function of the signal SIGCHLD when the child process terminates.

Act.sa_handler = sigchld_handler; // Call the wait function to wait for the child process to exit.

act.sa_flags = SA_NOCLDSTOP;

act.sa_mask = 0;

act.sa_restorer = NULL;

sigaction(SIGCHLD, &act, 0);

 

2. Mount the file system framework created during the kernel boot process to the appropriate directory.

    mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755");

   …

    mount("devpts", "/dev/pts", "devpts", 0, NULL);

    mount("proc", "/proc", "proc", 0, NULL);

mount("sysfs", "/sys", "sysfs", 0, NULL);

   

3. open_devnull_stdio() sets the standard input, output, and error device of the init process to the newly created device node /dev/__null__.

4. log_init(), create and open the device node /dev/__kmsg__.

5. Read and parse the rc configuration file.

5.1 Read the boot mode from the file /sys/class/BOOT/BOOT/boot/boot_mode: Factory Mode, '4'; ATE Factory Mode, '6'. See if it is a facatory mode.

5.2 If so, you need to read and parse two files: init.factory.rc and init.rc.

5.3 If it is a normal startup, read init.rc temporarily.

Here, when reading the parsing file, it is parsed in the smallest executable unit. The rules for writing the init.rc file's initialization script language can be found online. After parsing, it will not be executed immediately, but will be executed according to the conditions of its command itself before init enters the service loop.

   

6. Import the kernel cmdline, which is the parameter u-boot passes to the kernel, check whether it has androidboot.xxx (androidboot.mode, androidboot.hardware, etc.) parameters, if any, save it in the same name xxx (mode , hardware) in the global variable. Here is the hardware parameter. After exporting a part from the kernel, you need to export a part from /proc/cpuinfo to combine it into a complete hardware parameter, because the rc file for a specific platform will be read and parsed later.

7. Read the platform-specific initrc file, such as: init.mt6516.rc.

Need to pay attention to: For the service, here will create a struct service structure for each service, all linked into the list service_list, and will be started at the end of init.

   

8. Check if any of the parsed command lines belong to early-init. If so, add it to the action_queue of the linked list and execute it immediately.

 

9. The device_init() function will open the uevent netlink socket, traverse the /sys/class, /sys/block, /sys/devices directories, check the uevent files of all levels of the directory, and the processing will be sent by the kernel before the vold service is up. Device add, remove and other events.

 

10. property_init(), as its name implies, is property initialization. First create an anonymous shared memory area named system_properties, make a map read and write mapping to the init process, and the rest of the processes that share it have only read permissions. This prop_area structure is then passed to the property service via the global variable __system_property_area__.

Then call the function load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT) to load the compile-time generated attributes from the /default.prop file.

       

11. If there is an initlogo.rle file in the root directory, this is a blank image of two android words, which is read into fb and displayed on the LCD. Also output "A N D R O I D " to the serial port. If the image does not exist, there is no output for both.

 

12. Set the appropriate properties:

property_set("ro.factorytest", "0")

property_set("ro.serialno", serialno[0] ? serialno : "");

property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown");

property_set("ro.baseband", baseband[0] ? baseband : "unknown");

property_set("ro.carrier", carrier[0] ? carrier : "unknown");

property_set("ro.bootloader", bootloader[0] ? bootloader : "unknown");

 

property_set("ro.hardware", hardware);

snprintf(tmp, PROP_VALUE_MAX, "%d", revision);

property_set("ro.revision", tmp);

 

13. Start executing the command line with init as the trigger:

action_for_each_trigger("init", action_add_queue_tail);

drain_action_queue();

There have been executed eraly-init in front.

       

14. Start the property service: property_set_fd = start_property_service();

First read the attributes in the remaining three files: /system/build.prop, /system/default.prop, /system/default.prop, and then load the properties starting with persist. with the function load_persistent_properties(). Saved in the directory /data/property under the name of the file name.

Next create a socket interface called property_service (SOCK_STREAM), then enter the listening state, waiting for the property event to arrive.

   

15. Create a pair of sockets for signal processing.

socketpair(AF_UNIX, SOCK_STREAM, 0, s),signal_fd = s[0],signal_recv_fd = s[1]。

 

16. Execute the command of eraly-boot and boot as trigger

        action_for_each_trigger("early-boot", action_add_queue_tail);

        action_for_each_trigger("boot", action_add_queue_tail);

        drain_action_queue();

       

17. Execute the property setting statement in the init.rc starting with property: and enable the property triggering mode.

queue_all_property_triggers();

drain_action_queue();

       

Property_triggers_enabled = 1; // You can execute init statements that are conditional on properties.

       

1. The next step is to use the poll mechanism to listen to the dynamics of several fd created earlier.

struct pollfd ufds[4];

ufds[0].fd = device_fd;

ufds[0].events = POLLIN;

ufds[1].fd = property_set_fd;

ufds[1].events = POLLIN;

ufds[2].fd = signal_recv_fd;

ufds[2].events = POLLIN;

 

for(;;) {

     int nr, i, timeout = -1;

           

    for (i = 0; i < fd_count; i++)

        ufds[i].revents = 0;

    

Drain_action_queue(); //Execute the command that appears later in the action_queue list.

Restart_processes(); // Start all services for the first time, including later restarting these

service. Restart_service_if_needed() à service_start(svc, NULL) à fork()

    

     …

     nr = poll(ufds, fd_count, timeout);

     if (nr <= 0)

            continue;

    

     if (ufds[2].revents == POLLIN) {

            /* we got a SIGCHLD - reap and restart as needed */

            read(signal_recv_fd, tmp, sizeof(tmp));

            while (!wait_for_one_process(0))

                ;

            continue;

     }

    

     if (ufds[0].revents == POLLIN)

Handle_device_fd(device_fd); // Vold's netlink type socket

    

     if (ufds[1].revents == POLLIN)

Handle_property_set_fd(property_set_fd);//Attribute service socket

     if (ufds[3].revents == POLLIN)

            handle_keychord(keychord_fd);

}

Here init enters the dead loop and has been listening to the movement of the four file descriptors in ufds. If there is a POLLIN event, it will be processed accordingly, so init does not exit or enter the idle, but is treated as a service. Running. The fourth file descriptor is keychord_fd. It is not clear how to use this, but you can also start the service through it. You can refer to the source code.

The following is an example of init.rc, see the attachment init.rc

       

two,Various services started in init

The services started in init are in the order of init.rc, which are roughly:

Console: start a shell, code path: system/bin/sh, the source code contains commonly used shell commands, such as ls, cd, etc.

Adbd: start adb daemon, usually with a disabled option, indicating that it needs to be started by name, code path:system/bin/adb.

Servicemanager: This service manages all the binder services in the system. Code path: frameworks/base/cmds/servicemanager.

Vold: android udev, code path: system/vold.

Netd: start ntd daemon, code path: system/netd。

Debuggerd: start debug system, code path: system/core/debuggerd。

zygote: ['zaigəut] This is a very important service, which will be explained later. Start Android Java Runtime and start systemserver. Code path: frameworks/base/cmds/app_process.

media: add AudioFlinger, AudioPolicyService, MediaPlayerService and CameraService to servicemanager, at the same time start the management of the binder communication mechanism, relying on these two classes to complete the functions of the binder mechanism in the android middle layer: ProcessState and IPCThreadState. Code path: frameworks/base/media/mediaserver.

Bootanim: boot animation and ringtone, code path: frameworks/base/cmds/bootanimation.

 

Next is the modem service, such as: ccci_fsd, ccci_mdinit, pppd_gprs, pppd, gsm0710muxd, muxtestapp, sockcli, socksrv, muxreport, ril-daemon, etc., except for the first two, the following have disabled parameters, need to be by name start up.

 

Installd: start install package daemon, code path:

frameworks/base/cmds/installd。

There are many other services related to other hardware, such as BT, WIFI, etc.

 

2.1 servicemanager

This service process code is relatively simple, the function is also simple, c is implemented, used to manage all the binder services in the system, whether it is the local c++ implementation or the java language implementation, this process is required to be unified management, the most important management is , register to add services, get services.

These binder services must register their own binder entities in SMgr before they are exposed to the public. Other processes that need to use the binder service function must first obtain the corresponding reference number of the binder entity in the binder service in SMgr through SMgr. (The reference number of the binder is similar to the concept of the file descriptor in the process, and the valid range is limited to this process).

 

#define BINDER_SERVICE_MANAGER ((void*) 0)  

int main(int argc, char **argv) 

    struct binder_state *bs; 

    void *svcmgr = BINDER_SERVICE_MANAGER;  

    bs = binder_open(128*1024);   

// Open the binder device, mmap maps the binder receive buffer of the current process, and returns a binder_state structure.

    if (binder_become_context_manager(bs)) {     

/ / This function is used to set the current process to the service management process MSgr, the whole system is this one.

       …

    } 

    svcmgr_handle = svcmgr;    

    binder_loop(bs, svcmgr_handler);  

/*svcmgr_handler As a processing function, the operations that can be completed are: get the service, check if the service exists, add the service, and list the service list. The most used ones are acquisition and addition. */

    return 0; 

}

   

    2.2 zygote

    The zygote service process is also called the incubation process. In the linux user space, the process app_process will do

Some zygote process startup pre-work, such as starting the runtime runtime environment (instance), parameter decomposition, setting the startSystemServer flag, and then using runtime.start () to execute the zygote service code, in fact, simple point is that zygote grabbed app_process The body of this process, changed the name, replaced the latter code with the main function of zygote, which smoothly passed the zygote service process. So we use ps to see all the processes in the console, we will not see app_process, instead it is zygote.

       

The previous runtime.start() function is actually the class function AndroidRuntime::start(), in

In this function, a virtual machine instance is created and started to execute the main function of the com.android.internal.os.ZygoteInit package. This main function will fork a child process to start systemserver, the parent process exists as a real incubation process, whenever the system requires an Android application, Zygote will receive the socket message FORK out a child process to execute the application. program. Because the Zygote process is generated at system startup, it will complete the initialization of the virtual machine, the loading of the library, the loading and initialization of the preset class library, and can be quickly created when the system needs a new virtual machine instance. A virtual machine comes out.

   

Each Android application runs in a Dalvik virtual machine instance, and each virtual machine instance is a separate process space. Virtual machine threading mechanism, memory allocation and management, Mutex and so on are all dependent on the underlying linux implementation. So the android application creates a thread that will actually call the linux thread creation interface, and the virtual machine's process share a virtual machine instance to execute the java code.

   

2.2.1 app_process

The following focuses on the zygote service, the source code is located in frameworks/base/cmds/app_process.

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

Parameters: /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

 

int main(int argc, const char* const argv[])

{

    …

AppRuntime runtime; // This starts the runtime runtime environment (instance). AppRuntime is a subclass of AndroidRuntime. When the object is created, the constructors of the base and subclasses are called in turn.

    const char *arg;

    const char *argv0;

    …

    argv0 = argv[0];

    // ignore argv[0]

    argc--;

argv++;

/*

    argc = 4;

argv[0] = “-Xzygote”;

argv[1] = “/system/bin”;

argv[2] = “--zygote”;

argv[3] = “–start-system-server”;

*/

Int i = runtime.addVmArguments(argc, argv); // Find the first parameter in the argument that is not a single-start argument, here is obviously the second argument: /system/bin

    if (i < argc) {

runtime.mParentDir = argv[i++]; // Save the command directory in mParentDir, then i = 2.

    }

    if (i < argc) {

        arg = argv[i++];

If (0 == strcmp("--zygote", arg)) { // Usually this branch is true

            bool startSystemServer = (i < argc) ?

                    strcmp(argv[i], "--start-system-server") == 0 : false;

// startSystemServer = true , this bool variable determines whether systemserver is started when runtime.start is executed.

            setArgv0(argv0, "zygote");

            set_process_name("zygote");

            runtime.start("com.android.internal.os.ZygoteInit",

                startSystemServer);

        } else {

... // only start AndroidRuntime

        }

    }else{

... // error handling

    }

} // main()

   

    2.2.2 AndroidRuntime

Further analysis below

    runtime.start("com.android.internal.os.ZygoteInit",startSystemServer);

Located in the file frameworks/base/core/jni/AndroidRuntime.cpp, the class is defined in:

    frameworks/base/include/android_runtime/AndroidRuntime.h

    void AndroidRuntime::start(const char* className,

const bool startSystemServer){

LOGD("\n>>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<\n");

    JNIEnv* env;

    …

/* start the virtual machine , mJavaVM is a private variable of class AndroidRuntime, env local variable in the function */

    if (startVm(&mJavaVM, &env) != 0)

        goto bail;

 

/* Register android functions. Register the JNI local interface with the newly created virtual machine. Frameworks/base/core/jni/ All jni interfaces in this directory. */

    if (startReg(env) < 0) {

        …

goto bail;

}

 

/ / Before starting the virtual machine, you need to construct a parameter array of java form, as follows:

jclass stringClass;

jobjectArray strArray;

    jstring classNameStr;

    jstring startSystemServerStr;

 

stringClass = env->FindClass("java/lang/String");

strArray = env->NewObjectArray(2, stringClass, NULL);

classNameStr = env->NewStringUTF(className);

env->SetObjectArrayElement(strArray, 0, classNameStr);

startSystemServerStr = env->NewStringUTF(startSystemServer ?

                                                 "true" : "false");

env->SetObjectArrayElement(strArray, 1, startSystemServerStr);

/*  strArray[0] = “com.android.internal.os.ZygoteInit”

    strArry[1] = “true”*/

 

/*

 * Start VM.  This thread becomes the main thread of the VM, and will

 * not return until the VM exits.

 */

jclass startClass;

jmethodID startMeth;

 

slashClassName = strdup(className);

for (cp = slashClassName; *cp != '\0'; cp++)

    if (*cp == '.')

*cp = '/'; // Replace the package name with a path

 

startClass = env->FindClass(slashClassName); // find this package according to the path

// com.android.internal.os.ZygoteInit, this class is in the file

            // com/ndroid/nternal/os/ZygoteInit.java

if (startClass == NULL) {

     LOGE("JavaVM unable to locate class '%s'\n", slashClassName);

     /* keep going */

 } else {

/* This parameter determines the main function we are executing next to zygoteInit.java. */

        startMeth = env->GetStaticMethodID(startClass, "main",

            "([Ljava/lang/String;)V");

        if (startMeth == NULL) {

            LOGE("JavaVM unable to find main() in '%s'\n", className);

            /* keep going */

        } else {

            env->CallStaticVoidMethod(startClass, startMeth, strArray);

// The virtual machine starts and will call the main function of the com.android.internal.os.ZygoteInit package.

}

}

 

LOGD("Shutting down VM\n");

    if (mJavaVM->DetachCurrentThread() != JNI_OK)

        LOGW("Warning: unable to detach main thread\n");

    if (mJavaVM->DestroyJavaVM() != 0)

        LOGW("Warning: VM did not shut down cleanly\n");

 

bail:

    free(slashClassName);

}

} // start()

   

    2.2.3 ZygoteInit

Enter the main function of the com.android.internal.os.ZygoteInit package below:

    frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

    public static void main(String argv[]) {

        try {

            …

            registerZygoteSocket(); // Registers a server socket for zygote command

            // load classs and resources

    preloadClasses();

    preloadResources();

    …

    // Do an initial gc to clean up after startup

Gc();/*Initialize GC garbage collection mechanism*/

 

/* Start systemserver by the second parameter startsystemserver=”true” passed from the front. In startSystemServer(), a new process will be named system_server, and the SystemServer.java file in the com.android.server package will be executed. The main function, source location: frameworks/base/services/java/com/android/server/SystemServer.java. */

if (argv[1].equals("true")) {

                startSystemServer(); ///*************

} else if(…)

 

if (ZYGOTE_FORK_MODE) {

runForkMode(); /* ZYGOTE_FORK_MODE is always flase */

} else {

runSelectLoopMode();/* The Zygote process enters an infinite loop and is no longer returned. The next zygote will run as an incubation service process. */

}

 

closeServerSocket();

}

        …

    } // end main()

   

From here on, the android startup is divided into two lines, which are:

startSystemServer(); ---------- Zygote's child process

runSelectLoopMode(); /* The Zygote process enters an infinite loop, no longer returns, and performs the incubation. */

 

2.2.4 systemserver

In the above startSystemServer() function, the following call is made:

    startSystemServer()

    à Zygote.forkSystemServer()

à The parent process returns true directly, and the child process executes handleSystemServerProcess()

        à RuntimeInit.zygoteInit(parsedArgs.remainingArgs);

        /*

         * Pass the remaining arguments to SystemServer.

         * "--nice-name=system_server com.android.server.SystemServer"

         */

/* The above RuntimeInit package is set in the file frameworks\base\core\java\com\android\internal\os\RuntimeInit.java */

à invokeStaticMain(startClass, startArgs)

/* This function is called to execute the main function of the startClass class with the argument system_server . */

à ZygoteInit.MethodAndArgsCaller(m, argv)

/* Get the main() function of the package com.android.server.SystemServer. Then execute. */

   

The following starts to call the main function of the com.android.server.SystemServer class, the source code is located at:

    frameworks/base/services/java/com/android/server/SystemServer.java 

   

This method is called from Zygote to initialize the system. This will cause the nativeservices (SurfaceFlinger, AudioFlinger, etc..) to be started. After that it will call backup into init2() to start the Android services.

    // main--->init1(system_init)--->init2(systemThread)

native public static void init1(String[] args);

       

public static void main(String[] args) {

        ...

System.loadLibrary("android_servers");// libandroid_servers.so is compiled from the source code under the directory frameworks/base/services/jni

Init1(args); // init1 is actually a jni local interface, located in the file frameworks\base\services\jni\com_android_server_SystemServer.cpp file system_init() function

}

   

The init1 interface is located in com_android_server_SystemServer.cpp:

extern "C" int system_init();

static void android_server_SystemServer_init1(JNIEnv* env, jobject clazz){

    system_init();

}

 

static JNINativeMethod gMethods[] = {

    /* name, signature, funcPtr */

{"init1","([Ljava/lang/String;)V",(void*)

android_server_SystemServer_init1 },

};

The function system_init() is located in the file.

frameworks\base\cmds\system_server\library\System_init.cpp

extern "C" status_t system_init()

{

    …

    char propBuf[PROPERTY_VALUE_MAX];

    property_get("system_init.startsurfaceflinger", propBuf, "1");

    if (strcmp(propBuf, "1") == 0) {

        // Start the SurfaceFlinger

        SurfaceFlinger::instantiate();

    }

    if (!proc->supportsProcesses()) {

       

        // Start the AudioFlinger

        AudioFlinger::instantiate();

       

        // Start the media playback service

        MediaPlayerService::instantiate();

       

        // Start the camera service

        CameraService::instantiate();

       

        // Start the audio policy service

        AudioPolicyService::instantiate();

       

        //start appc service

        APPCService::instantiate();

    }

    …

    AndroidRuntime* runtime = AndroidRuntime::getRuntime();

    runtime->callStatic("com/android/server/SystemServer", "init2");

/ / Execute the init2 function of the com.android.server.SystemServer class

}

The init2 function of the com.android.server.SystemServer package opens a ServerThread thread:

public static final void init2() {

    Thread thr = new ServerThread();

    thr.setName("android.server.ServerThread");

    thr.start();

}

The run function of the ServerThread thread will start most of the android service in the system, and finally enter Loop.loop(),,,,(SystemServer.java)

public void run() {

    …

    // Critical services...

    try {

        …

        Slog.i(TAG, "Power Manager");

        power = new PowerManagerService();

        ServiceManager.addService(Context.POWER_SERVICE, power);

       

        Slog.i(TAG, "Activity Manager");

        context = ActivityManagerService.main(factoryTest);

       

        …

        Slog.i(TAG, "Package Manager");

        pm = PackageManagerService.main(context,

                factoryTest != SystemServer.FACTORY_TEST_OFF);

       

        …

        Slog.i(TAG, "Content Manager");

        ContentService.main(context,

             factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL);

       

        …

        Slog.i(TAG, "Battery Service");

        battery = new BatteryService(context);

        ServiceManager.addService("battery", battery);

        …

The rest of the addservice process is similar, just start the different services, and started a lot later.

Services such as: Lights, Vibrator, Alarm, Sensor, Bluetooth, Input Method, NetStat, NetworkManagement, Connectivity, Mount, Notification, Audio, etc. After these services are all started.

        …

        …

... // the second half of the run() function

        // It is now time to start up the app processes...

Use xxx.systemReady() to notify each service that the system is ready.

        …

        ((ActivityManagerService)ActivityManagerNative.getDefault())

                .systemReady(new Runnable() {

         public void run() {

                …

                if (batteryF != null) batteryF.systemReady();

                if (connectivityF != null) connectivityF.systemReady();

                if (dockF != null) dockF.systemReady();

                if (uiModeF != null) uiModeF.systemReady();

                if (recognitionF != null) recognitionF.systemReady();

                Watchdog.getInstance().start();

                …

            }

        });

        …

        Looper.loop(); // Run the message queue in this thread。

        …

}

 

2.2.5 home interface startup

Home at

((ActivityManagerService)ActivityManagerNative.getDefault()).systemReady(.)

The function call is started, and the parameter of systemReady() is a callback code, as shown in the gray above.

The implementation of this function is in the file: ActivityManagerService.java.

public void systemReady(final Runnable goingCallback) {

    …

   

    if (mSystemReady) {

        if (goingCallback != null) goingCallback.run();

Return; // perform a callback

    }

    …

    resumeTopActivityLocked(null);

}

private final boolean resumeTopActivityLocked(HistoryRecord prev) {

    …

    if (next == null) {

        // There are no more activities!  Let's just start up the

        // Launcher...

        return startHomeActivityLocked();

    }

    …

}

private boolean startHomeActivityLocked() {

    …

    if (aInfo != null) {

        …

        if (app == null || app.instrumentationClass == null) {

                intent.setFlags(intent.getFlags() |

                                Intent.FLAG_ACTIVITY_NEW_TASK);

                startActivityLocked(null, intent, null, null, 0, aInfo,

Null, null, 0, 0, 0, false, false); // start home here

         }

     }

    …

}

three,Android start icon

Reference URL:

1.       

2.       

3.        http://www.docin.com/p-191202348.html

4.       

 


Reprinted at: https://my.oschina.net/u/586684/blog/194733

Intelligent Recommendation

Responsive design for mobile web

Theoretical basis: adaptation problem:screen sizeDifferent Solution: Percentage, adaptive layout, called streaming layout, and also need to set the viewport viewport on the mobile side to achieve the ...

mybatis-plus spring with the simple use

The first step is spring integrated mybatis-plus, mybatis-plus official websitehttps://mp.baomidou.com/guide/install.html#releaseIn fact, there is a detailed introduction. But as the first contact wit...

Federal Study (2): Convolution Neural Network Realized Fedavg Federal Algorithm by Bottom API

Article catalog 1. Import the required library 2, import and process data 3, define the batch data type 4, define model types 4.1 Getting the parameter shape of each layer of neural network 4.2 Get th...

More Recommendation

Open the door to a new world

   My god, it’s the first time I write a blog, so nervous. . . . . . In the first three years of my career, I was a front-end engineer. What I deal with every day is code, HTML, CSS, JS, various...

Package adaptive text length according to EL-Tooltip

Article catalog 1. Thoughts and Principles Overview 2. Introduction 3. Component implementation 4. Example of use 5. Description Component is based onelement uiof el-tooltipaccomplish; el-tooltipIt do...

Arithmetic operator

Arithmetic operator The arithmetic operator is used in mathematical expressions, which is the same as in the album (or other computer language). Arithmetic operator and its meaning The number of arith...

WordPress theme recommendation: dual light and dark theme/multi-toolbar/separate setting of column-BoxStyle

original After the site was built, the biggest problem was choosing the theme. As the saying goes: You don’t need to write an article, and you have to change the topic. After reading several top...

Fourth paradigm internship experience and harvest

Internship: AI product development Project 1: Equipment Equity According to the interface API demand document, create tasks, start tasks, view task status, and download tasks, etc. Tools: Postman, Pyc...

Copyright  DMCA © 2018-2026 - All Rights Reserved - www.programmersought.com  User Notice

Top