Android系统启动流程

2023-11-16

总结

android系统是基于linux的,启动大致分为如下几个阶段
在这里插入图片描述

  • BootRom
    启动电源以及系统启动。当电源按下时,引导芯片代码从预定义的地方(固化在ROM中)开始执行。加载引导程序BootLoader到RAM,然后执行。
  • 引导程序Bootloader
    引导程序BootLoader是在android操作系统开始运行前的一个小程序,他的主要作用是把系统OS拉起来并运行。
  • Linux kernel启动
    当内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。当内核完成系统设置时,它首先在系统文件中寻找init.rc文件,并启动init进程。
  • init进程启动
    初始化和启动属性服务,并且启动Zygote进程。
  • Zygote进程启动
    创建Java虚拟机并为Java虚拟机注册JNI方法,创建服务器端Socket,启动SystemServer进程。
  • systemserver进程启动
    启动Binder线程池和SystemServiceManager,并且启动各种系统服务。
  • Launcher进程启动
    被SystemServer进程启动的AMS会启动Launcher,Launcher启动后会将已安装应用的快捷图标显示到界面上

下面基于aosp11-13进行分析,因为多次阅读和使用的源码不一样,可能出现的代码并不是同一个Android版本代码

1.rc脚本语法规则

源码中提供了一个官方文档(aosp\system\core\init\README.md)来说明rc脚本使用规则。rc脚本由Actions、Commands、Services、Options、Improts组成。

这部分内容可以参考《深入理解Android内核设计思想(第2版)》—第七章 Android启动过程

  • Actions:当达到某个触发条件时,执行命令,即响应某事件的过程

    Actions take the form of:
    
        on <trigger> [&& <trigger>]*  // 触发条件
           <command>  // 执行命令
           <command>
           <command>
    
  • Commands:命令将在所属事件发生时被一个个地执行,rc脚本中定义了许多命令,如

    boot:init程序启动后触发的第一个事件
    class_start <serviceclass>:启动服务
    class_stop <serviceclass>:停止服务
    ......
    
  • Services:可执行程序

    Services take the form of:
    
        service <name> <pathname> [ <argument> ]*
           <option>
           <option>
           ...
    
    // name:服务名称
    // pathname:服务路径
    // argument:启动服务传递的参数
    // option:对服务的约束项
    
  • Options:服务修饰符,影响init进程运行服务的方式和时间。即上面的

    critical:表明这是对设备至关重要的一个服务。如果它在指定的时间内退出超过四次,则设备将重启进入恢复模式
    onrestart:重启服务
    
    
  • Imports:引入其他rc配置文件,语法格式为import <path>

2.init进程启动

init是一个Linux程序,位于设备中/system/bin/init。

通过解析init.rc脚本(system\core\rootdir\init.rc)来构建出系统的初始运行形态,其他Android系统服务 程序大多是在这个rc脚本中描述并被相继启动的。

init源码位于system/core/init,从编译脚本中可以看出,init程序分为两个阶段:init_first_stage和init_second_stage

// /system/core/init/Android.bp
phony {
    name: "init",
    required: [
        "init_second_stage",
    ],
}

cc_binary {
    name: "init_second_stage",
    ...
}

// /system/core/init/Android.mk
LOCAL_MODULE := init_first_stage
LOCAL_MODULE_STEM := init
LOCAL_FORCE_STATIC_EXECUTABLE := true
include $(BUILD_EXECUTABLE)


LOCAL_MODULE := init_system
LOCAL_REQUIRED_MODULES := \
   init_second_stage \

include $(BUILD_PHONY_PACKAGE)

LOCAL_MODULE := init_vendor
ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
LOCAL_REQUIRED_MODULES := \
   init_first_stage \

endif
include $(BUILD_PHONY_PACKAGE)

在系统启动时过滤init进程相关日志,发现init开始是0号进程,后面变为1号进程,整个过程为:

由0号进程创建1号进程(内核态),1号负责执行内核的初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟主存管理的内核线程。随后,1号进程调用execve()运行可执行程序init,并演变成用户态1号进程,即init进程。

Linux系统中的init进程(pid=1)是除了idle进程(pid=0,也就是init_task)之外另一个比较特殊的进程,它是Linux内核开始建立起进程概念时第一个通过kernel_thread产生的进程,其开始在内核态执行,然后通过一个系统调用,开始执行用户空间的/sbin/init程序,期间Linux内核也经历了从内核态到用户态的特权级转变。

2023-03-13 00:37:52.600     0-0     init                           kernel                               I  init first stage started!
2023-03-13 00:37:57.209     0-0     init                           kernel                               I  init second stage started!
2023-03-13 00:37:59.823     0-0     <no-tag>                       kernel                               W  init (pid 1) is setting deprecated v1 encryption policy; recommend upgrading to v2.
2023-03-13 00:38:05.672     1-1     /system/bin/init               pid-1                                W  type=1107 audit(0.0:6): uid=0 auid=4294967295 ses=4294967295 subj=u:r:init:s0 msg='avc: denied { set } for property=vendor.wlan.firmware.version pid=296 uid=1010 gid=1010 scontext=u:r:hal_wifi_default:s0 tcontext=u:object_r:vendor_default_prop:s0 tclass=property_service permissive=0' b/131598173

可以通过ps命令查看init进程号为1,父进程号为0。

generic_x86:/ # ps -ef -Z | grep init                                                                                                                    
u:r:init:s0                    root              1      0 0 16:14:24 ?     00:00:03 init second_stage
u:r:vendor_init:s0             root            120      1 0 16:14:30 ?     00:00:00 init subcontext u:r:vendor_init:s0 15

我们看init程序的main方法,当没有传参时会执行first_init阶段,当参数为second_stage,会执行second_stage阶段。这个两个阶段源码比较清晰,就不贴出来了。

// system/core/init/main.cpp
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#endif
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }

    if (argc > 1) {
        if (!strcmp(argv[1], "subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();

            return SubcontextMain(argc, argv, &function_map);
        }

        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }

        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }

    return FirstStageMain(argc, argv);
}

首先会执行FirstStageMain,最终运行形态在第二阶段:

generic_x86:/ # cat proc/1/cmdline                                                                                                                                                                                                       
/system/bin/initsecond_stage

init_first_stage

主要执行了system/core/init/first_stage_init.cpp:FirstStageMain()​,主要做了下面几种事情:

  • 挂载必要的文件系统

  • 初始化kmsg

  • 加载内核模块、驱动

  • 创建设备节点

  • 再次运行init程序,参数为“selinux_setup”,设置SELinux环境

    // system/core/init/first_stage_init.cpp
    int FirstStageMain(int argc, char** argv) {
        // ......
        const char* path = "/system/bin/init";
        const char* args[] = {path, "selinux_setup", nullptr};
        auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        close(fd);
        execv(path, const_cast<char**>(args));
        PLOG(FATAL) << "execv(\"" << path << "\") failed";
        return 1;
    }
    
  • 设置SELinux环境完成后,执行"second_stage"

    int SetupSelinux(char** argv) {
        SetStdioToDevNull(argv);
        InitKernelLogging(argv);
    
        if (REBOOT_BOOTLOADER_ON_PANIC) {
            InstallRebootSignalHandlers();
        }
    
        boot_clock::time_point start_time = boot_clock::now();
    
        MountMissingSystemPartitions();
    
        // Set up SELinux, loading the SELinux policy.
        SelinuxSetupKernelLogging();
        SelinuxInitialize();
    
        setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);
    
        const char* path = "/system/bin/init";
        const char* args[] = {path, "second_stage", nullptr};
        execv(path, const_cast<char**>(args));
    
        PLOG(FATAL) << "execv(\"" << path << "\") failed";
    
        return 1;
    }
    

init_second_stage

int SecondStageMain(int argc, char** argv)​是在system/core/init/init.cpp中,主要做的的事情如下:

  • 设置/proc/1/oom_score_adj​,值为-1000
  • 启动Property​服务
  • 挂载一些tmpfs​中文件目录
  • 启动selinux环境
  • 启动PropertyService
  • 启动Treble框架
  • fork subContext
  • 启动系统服务

因此,整个流程图如下:

3.ServiceManager启动

ServiceManager是在rc脚本中启动的:

 // system\core\rootdir\init.rc
on init
   # Start essential services.
   start servicemanager

servicemanager服务定义在frameworks\native\cmds\servicemanager\servicemanager.rc。可以看到,servicemanger是一个Linux程序,它在设备中的存储路径是/system/bin/servicemanager,源码路径则是/frameworks/native/cmds/servicemanager。

从rc脚本中可以知道,servicemanager作为系统核心服务,遇到异常重启时会同时启动audioserver等其他核心服务。

// frameworks\native\cmds\servicemanager\servicemanager.rc
service servicemanager /system/bin/servicemanager
    class core animation
    user system
    group system readproc
    critical
    onrestart restart apexd
    onrestart restart audioserver
    onrestart restart gatekeeperd
    onrestart class_restart --only-enabled main
    onrestart class_restart --only-enabled hal
    onrestart class_restart --only-enabled early_hal
    task_profiles ServiceCapacityLow
    shutdown critical

servicemanager启动后做了什么事情,详情见ServiceManager(Native)启动分析

4.Zygote进程启动

zygote进程也是由init进程解析rc脚本启动:

 // system\core\rootdir\init.rc
on late-init
# Now we can start zygote for devices with file based encryption
trigger zygote-start

根据不同的硬件类型分了几种(可以通过ro.hardware系统属性查看),下面以init.zygote64.rc为例进行说明
在这里插入图片描述

// system\core\rootdir\init.zygote64.rc
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart media.tuner
    onrestart restart netd
    onrestart restart wificond
    task_profiles ProcessCapacityHigh
    critical window=${zygote.critical_window.minute:-off} target=zygote-fatal

从zygote的path路径可以看出,它所在的程序名叫 “app_process64”,而不像ServiceManager一样在一个独立的程序 中。通过指定–zygote参数,app_process可以识别出用户是否需要启动zygote。app_process源码位于aosp\frameworks\base\cmds\app_process,从Android.bp文件中可以看出,针对不同的平台生成相应的目标

// frameworks\base\cmds\app_process\Android.bp
cc_binary {
    name: "app_process",

    srcs: ["app_main.cpp"],

    multilib: {
        lib32: {
            suffix: "32",
        },
        lib64: {
            suffix: "64",
        },
    },
    ....
}

接着我们看app_main.cpp的main(),整个过程大致分为下面几个阶段:

  • 创建AndroidRuntime
  • 通过不同的传参执行不同的逻辑,如参数为–zygote时,则启动Zygote进程
  • 启动了ZygoteInit
  • forkSystemServer
  • 开启uds
// frameworks/base/cmds/app_process/app_main.cpp
int main(int argc, char* const argv[])
{
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    // Process command line arguments
    // ignore argv[0]
    argc--;
    argv++;

    // Everything up to '--' or first non '-' arg goes to the vm.
    //
    // The first argument after the VM args is the "parent dir", which
    // is currently unused.
    //
    // After the parent dir, we expect one or more the following internal
    // arguments :
    //
    // --zygote : Start in zygote mode
    // --start-system-server : Start the system server.
    // --application : Start in application (stand alone, non zygote) mode.
    // --nice-name : The nice name for this process.
    //
    // For non zygote starts, these arguments will be followed by
    // the main class name. All remaining arguments are passed to
    // the main method of this class.
    //
    // For zygote starts, all remaining arguments are passed to the zygote.
    // main function.
    //
    // Note that we must copy argument string values since we will rewrite the
    // entire argument block when we apply the nice name to argv0.
    //
    // As an exception to the above rule, anything in "spaced commands"
    // goes to the vm even though it has a space in it.
    const char* spaced_commands[] = { "-cp", "-classpath" };

   // .......

    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;
    ++i;  // Skip unused "parent dir" argument.
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {
            className.setTo(arg);
            break;
        } else {
            --i;
            break;
        }
    }
   // .......
    if (!niceName.isEmpty()) {
        runtime.setArgv0(niceName.string(), true /* setProcName */);
    }

    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (!className.isEmpty()) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    }
}

我们看runtime.start​逻辑:

  • 启动VM:startVm()
  • 注册Android相关的vm方法:startReg()
  • 通过jni创建java层​对象​
// frameworks/base/core/jni/AndroidRuntime.cpp

/*
 * Start the Android runtime.  This involves starting the virtual machine
 * and calling the "static void main(String[] args)" method in the class
 * named by "className".
 *
 * Passes the main function two arguments, the class name and the specified
 * options string.
 */
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    static const String8 startSystemServer("start-system-server");
    // Whether this is the primary zygote, meaning the zygote which will fork system server.
    bool primary_zygote = false;

    /*
     * 'startSystemServer == true' means runtime is obsolete and not run from
     * init.rc anymore, so we print out the boot start event here.
     */
    for (size_t i = 0; i < options.size(); ++i) {
        if (options[i] == startSystemServer) {
            primary_zygote = true;
           /* track our progress through the boot sequence */
           const int LOG_BOOT_PROGRESS_START = 3000;
           LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,  ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
        }
    }
    /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
        return;
    }
    onVmCreated(env);

    /*
     * Register android functions.
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

    /*
     * We want to call main() with a String array with arguments in it.
     * At present we have two arguments, the class name and an option string.
     * Create an array to hold them.
     */
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    stringClass = env->FindClass("java/lang/String");
    assert(stringClass != NULL);
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
    assert(strArray != NULL);
    classNameStr = env->NewStringUTF(className);
    assert(classNameStr != NULL);
    env->SetObjectArrayElement(strArray, 0, classNameStr);

    for (size_t i = 0; i < options.size(); ++i) {
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        assert(optionsStr != NULL);
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    }

    /*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */
    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    free(slashClassName);

    ALOGD("Shutting down VM\n");
    if (mJavaVM->DetachCurrentThread() != JNI_OK)
        ALOGW("Warning: unable to detach main thread\n");
    if (mJavaVM->DestroyJavaVM() != 0)
        ALOGW("Warning: VM did not shut down cleanly\n");
}

通过上面分析,可以知道会分别创建Java层RuntimeInit和ZygoteInit对象。

当ZygoteInit创建时,会开启UDS,等在app执行fork操作,并且会创建SystemServer。ZygoteInit日志如下:

03-19 17:09:33.767   263   263 D Zygote  : begin preload
03-19 17:09:33.767   263   263 I Zygote  : Calling ZygoteHooks.beginPreload()
03-19 17:09:35.065   263   263 I zygote  : VMRuntime.preloadDexCaches finished
03-19 17:09:35.142   263   263 D Zygote  : end preload
03-19 17:09:35.270   263   263 D Zygote  : Forked child process 475
03-19 17:09:35.270   263   263 I Zygote  : System server process 475 has been created
03-19 17:09:35.271   263   263 I Zygote  : Accepting command socket connections
03-19 17:09:35.861   475   475 I SystemServerTiming: InitBeforeStartServices
03-19 17:09:35.862   475   475 I SystemServer: Entered the Android system server!
03-19 17:09:36.155   475   475 I SystemServerTiming: StartServices
03-19 17:09:42.807   475   475 I SystemServer: Making services ready


03-19 17:09:36.425   475   475 I SystemServerTiming: StartActivityManager
03-19 17:09:36.425   475   475 I SystemServiceManager: Starting com.android.server.wm.ActivityTaskManagerService$Lifecycle
03-19 17:09:36.445   475   475 I SystemServiceManager: Starting com.android.server.am.ActivityManagerService$Lifecycle
03-19 17:09:36.675   475   475 D SystemServerTiming: StartActivityManager took to complete: 251ms

03-19 17:09:43.016   475   475 I SystemServerTiming: StartSystemUI
03-19 17:09:43.019   475   475 D SystemServerTiming: StartSystemUI took to complete: 3ms

03-19 17:09:43.305   475   475 I SystemServerTiming: startPersistentApps

03-19 17:09:43.314   475   475 D SystemServerTiming: startHomeOnAllDisplays took to complete: 7ms
03-19 17:09:43.306   475   475 I ActivityTaskManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000100 cmp=com.android.settings/.FallbackHome} from uid 0
03-19 17:09:43.306   475   475 I SystemServerTiming: startHomeOnAllDisplays
03-19 17:09:50.945   475   489 I ActivityTaskManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000100 cmp=com.android.launcher3/.uioverrides.QuickstepLauncher} from uid 0

5.Launcher启动


参考文档:

  • 《深入理解Android内核设计思想(第2版)》

  • https://blog.csdn.net/L12ThMicrowave/article/details/126303805

  • https://zhuanlan.zhihu.com/p/585387856

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android系统启动流程 的相关文章

随机推荐

  • PCL-OpenCV冲突的解决方案

    测试LIO SAM 编译期报错 error field param k has incomplete type flann SearchParams 查了github 把 usr include pcl 1 7 pcl kdtree kdt
  • Dialog的弹出位置控制

    Dialog一般出现都是屏幕中心 但有的时候我们希望它能在屏幕左侧 或者右侧亦或者是屏幕顶部 这时候我们就需要用的Dialog getWindow getAttributes 方法啦 dialog new Dialog getActivit
  • Qwt使用总结,初步整理

    最近研究了一些Qwt 至此总结Qwt的认识以便以后的备忘 从什么方面开始说起呢 从这个类是什么 可以用来做什么 关于Qwt的简介就不说了 主要说说其中用到的几个类吧 QwtPlot 是一个2D画图控件 Widget 继承于QFram和Qwt
  • Mybatis Plus 3.1.0枚举类处理器重写TypeHandler

    我的环境 Springboot 2 13 Mybatis Plus 3 1 Oracle 11g 驱动版本oracle6 1 我尝试用 Mybatis Plus 3 1 以上版本 如 3 2 3 3 时 oracle6 驱动无法适配 2 枚
  • JAVA线程的中断

    每个线程都有一个boolean类型的标志来表明线程是否发生了中断 并且包含了中断相关的函数 interrupt 用于设置线程的中断状态为true isInterrupted 用于返回线程的中断状态 interrupted 方法用于清除中断状
  • cast函数_QT槽函数获取信号发送对象

    Qt 在槽函数中获取信号发送对象 Qt中提供了一个函数 qobject cast QObject object 可以通过这个函数判断信号发出对象 Qt 帮助文档的解释 Returns the given object cast to typ
  • 通讯录(C语言)

    结合指针 结构体 枚举 实现增删改查 test c 用于测试主函数 contact h 用于函数的声明 contact c 用于函数的实现 test c 主要思路 用do while 实现基本分支结构 并用枚举类型 规定出选择以增加代码可读
  • yarn创建vue项目报错解决

    1 使用yarn create vue创建项目时报如下错误 2 原因是由于安装包目录和bin目录不在统一磁盘下 查看方法 查看bin目录 yarn global bin 查看安装包目录 yarn global dir 3 解决 1 将yar
  • 代码审计工具之Fortify安装以及初步使用

    目录 1 Fortify Fortify工具介绍 1 Fortify Fortify工具介绍 Fortify SCA 是一个静态的 白盒的软件源代码安全测试工具 它通过内置的五大主要分析引擎 数据流 语义 结构 控制流 配置流等对应用软件的
  • 【Transformer】9、CrossFormer:A versatile vision transformer based on cross-scale attention

    文章目录 一 背景 二 动机 三 方法 3 1 Cross scale Embedding Layer CEL 3 2 Cross former Block 3 2 1 Long Short Distance Attention LSDA
  • 解决RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment

    RuntimeError CUDA unknown error this may be due to an incorrectly set up environment e g changing env variable CUDA VISI
  • IDEA代码规范插件(CheckStyle插件、alibaba插件)

    IDEA代码规范插件 CheckStyle插件 alibaba插件 代码规范插件 CheckStyle插件 alibaba插件 代码规范插件 CheckStyle插件 1 安装 打开idea的file settings plugins 再搜
  • 关于 微软商店无法加载页面 显示错误代码0x80131500的解决办法

    目录 一 误删系统文件导致Microsoft Store无法打开 1 运行 SFC 和 DISM 2 尝试修复或者重置微软应用商店 3 重新部署 Microsoft Store 4 运行Windows疑难解答 5 对系统镜像进行无损修复 二
  • 渗透测试——提权方式总结

    内容整理自网络 一 什么是提权 提权就是通过各种办法和漏洞 提高自己在服务器中的权限 以便控制全局 Windows User gt gt System Linux User gt gt Root 二 怎样进行提权 提权的方式有哪些 1 系统
  • AI算法工程师面试题基础精选

    AI算法工程师的相关面试题包括机器学习 深度学习以及强化学习等等 在面试时由于涉及范围比较广泛 一般面试官不会问一些比较深比较偏的问题 一般都会结合你经手的项目或者在校期间的项目进行一些算法的基础问题进行提问 在这里我们对在面试中常见中的基
  • 分享CSS3里box-shadow属性的使用方法,包括内阴影box-shadow:inset

    一 box shadow语法 box shadow none inset 可选值 不设置 为外投影 设置 为内投影 x offset 阴影水平偏移量 正方向为right y offset 阴影垂直偏移量 正方向为bottom blur ra
  • 记录好项目D16

    记录好项目 你好呀 这里是我专门记录一下从某些地方收集起来的项目 对项目修改 进行添砖加瓦 变成自己的闪亮项目 修修补补也可以成为毕设哦 本次的项目是个电影购票系统 一 系统介绍 前台 普通用户注册 登录 注销 用户信息修改 邮箱 密码 头
  • Qt编译时,出现 first defined here,原因及解决方法

    场景 今天想着把之前写过的模块 都整合到一起 结果一编译程序就出现这个错误 原因 因为头文件出现重复包含了 后来我想了一下 我每个模块都是独立编写的 怎么会重复呢 然后去了pro文件里看了一下 里面果然有两个一模一样的头文件名 QT诚不欺我
  • Newtonsoft.Json基本使用

    Newtonsoft Json基本使用 使用强类型进行序列化反序列化 准备一个学生类 public class Student public string Name get set public int Age get set public
  • Android系统启动流程

    文章目录 总结 1 rc脚本语法规则 2 init进程启动 init first stage init second stage 3 ServiceManager启动 4 Zygote进程启动 5 Launcher启动 总结 android