u-boot之内核是怎么启动的
在u-boot之start_armboot函数分析已经分析过了整个程序框架,但只是说了下什么时候运行内核,并没有具体说明是怎么执行内核的。内核启动分以下几个步骤说明:
1、启动参数bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0说明
2、run_command函数是怎么执行命令的
3、u-boot给内核传递的参数说明
4、内核启动流程
1、启动参数bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0说明
这个参数分了两条uboot命令首先从kernel区拷贝相应大小的内核到内存0x30007FC0 处;然后执行 bootm 0x30007FC0命令,这条命令后面会详细解释流程。执行了这条命令后就可以从0x30007FC0处启动内核了。这里需要再说明一下其实真正的内核位于0x30008000处,因为前面64字节是内核的一些信息,具体定义如下:
typedef struct image_header {
uint32_t ih_magic; /* Image Header Magic Number *///内核的编号
uint32_t ih_hcrc; /* Image Header CRC Checksum */ //内核头部数据的CRC校验
uint32_t ih_time; /* Image Creation Timestamp */ //内核建立的时间戳
uint32_t ih_size; /* Image Data Size */ //内核的大小
uint32_t ih_load; /* Data Load Address */ //内核的装载地址
uint32_t ih_ep; /* Entry Point Address */ //内核切入点地址
uint32_t ih_dcrc; /* Image Data CRC Checksum */ //内核数据的CRC校验
uint8_t ih_os; /* Operating System *///内核是什么操作系统
uint8_t ih_arch; /* CPU architecture */ //内核的CPU架构
uint8_t ih_type; /* Image Type */ //内核映像文件类型
uint8_t ih_comp; /* Compression Type *///压缩类型
uint8_t ih_name[IH_NMLEN]; /* Image Name *///内核映像文件名称
} image_header_t;
2、run_command函数是怎么执行命令的
run_command函数在Main.c (common)中,这个函数是整个U-BOOT命令的核心函数。uboot的链接文件中存在着.u_boot_cmd段,这个段的内容就是存放的所有的UBOOT命令
这个段的定义在command.h(include)中,如下:其中
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd"))) #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
//分解为=>U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) cmd_tbl_t __u_boot_cmd_name __attribute__ ((unused,section (".u_boot_cmd"))) = {name, maxargs, rep, cmd, usage, help}
//name为命令的名字
//maxargs为命令的最大数量
//rep为命令是否可以重复,表示按下回车是否可以运行上一次的命令
//cmd表示具体命令的执行函数指针,这个函数不在.u_boot_cmd这个段内,只是函数的指针存放在.u_boot_cmd这个段内
//usage表示简短的用法说明
//help表示长的说明
struct cmd_tbl_s {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
int repeatable; /* autorepeat allowed? */
/* Implementation function */
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]);
char *usage; /* Usage message (short) */
#ifdef CFG_LONGHELP
char *help; /* Help message (long) */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
举个例子以bootm这个命令为例子,在u-boot控制台输入bootm命令后,u-boot代码会执行run_command(bootm, 0);这个函数。然后调用do_bootm函数。
U_BOOT_CMD(
bootm, CFG_MAXARGS, , do_bootm,
"bootm - boot application image from memory\n",
"[addr [arg ...]]\n - boot application image stored in memory\n"
"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
"\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
"\tWhen booting a Linux kernel which requires a flat device-tree\n"
"\ta third argument is required which is the address of the of the\n"
"\tdevice-tree blob. To boot that kernel without an initrd image,\n"
"\tuse a '-' for the second argument. If you do not pass a third\n"
"\ta bd_info struct will be passed instead\n"
#endif
);
照着执行bootm命令继续下去,run_command(bootm, 0)函数首先做一些参数的检查
clear_ctrlc(); /* forget any previous Control C *///清除前一条命令 if (!cmd || !*cmd) {//检查cmd命令是否存在
return -; /* empty command */
} if (strlen(cmd) >= CFG_CBSIZE) {//检查参数个数是否超过最大限制
puts ("## Command too long!\n");
return -;
} strcpy (cmdbuf, cmd);//将cmd全部拷贝到cmdbuf中
run_command(bootm, 0)继续运行,一直循环寻找str中的命令。str在初始化的时候就已经被赋为cmdbuf了。大致说明一下程序的流程:
a、首先就查询命令有几条
b、展开调用环境变量的命令
c、取得参数的个数
d、查找命令,从.u_boot_cmd段中找出符合的命令
e、执行命令的运行函数
while (*str) {//从输入的字符中取出有效命令 by andy /*
* Find separator, or string end
* Allow simple escape of ';' by writing "\;"
*/
for (inquotes = , sep = str; *sep; sep++) {//一直查询到最后一个字节为结束符
if ((*sep=='\'') && //查找连接符合
(*(sep-) != '\\'))
inquotes=!inquotes; //如果是连接符的话 if (!inquotes &&
(*sep == ';') && /* separator *///说明是多个连续的命令
( sep != str) && /* past string start */
(*(sep-) != '\\')) /* and NOT escaped */
break;
} /*
* Limit the token to data between separators
*/
token = str;
if (*sep) {//如果不止一条命令 by andy
str = sep + ; /* start of command for next pass *///指向下一条命令的开头,当前seq指向的是;
*sep = '\0';
}
else
str = sep; /* no more commands for next pass */
#ifdef DEBUG_PARSER
printf ("token: \"%s\"\n", token);
#endif /* find macros in this token and replace them */
process_macros (token, finaltoken);//展开环境变量 by andy,以$()或${}取得变量最终得到finaltoken /* Extract arguments */
if ((argc = parse_line (finaltoken, argv)) == ) {//取得变量个数以及变量的值存放在argv中,变量个数包括命令,所以应该大于0 by andy
rc = -; /* no command at all */
continue;
} /* Look up command in command table */
if ((cmdtp = find_cmd(argv[])) == NULL) {//查找命令,找到后返回的是cmd_tbl_t结构体指针 by andy
printf ("Unknown command '%s' - try 'help'\n", argv[]);
rc = -; /* give up after bad command */
continue;
} /* found - check max args */
if (argc > cmdtp->maxargs) {//验证参数的个数是否超过限制 by andy
printf ("Usage:\n%s\n", cmdtp->usage);
rc = -;
continue;
} #if (CONFIG_COMMANDS & CFG_CMD_BOOTD)
/* avoid "bootd" recursion */
if (cmdtp->cmd == do_bootd) {
#ifdef DEBUG_PARSER
printf ("[%s]\n", finaltoken);
#endif
if (flag & CMD_FLAG_BOOTD) {
puts ("'bootd' recursion detected\n");
rc = -;
continue;
} else {
flag |= CMD_FLAG_BOOTD;
}
}
#endif /* CFG_CMD_BOOTD */ /* OK - call function to do the command */
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != ) {//调用命令函数执行 by andy
rc = -;
} repeatable &= cmdtp->repeatable; /* Did the user stop this? */
if (had_ctrlc ())//是否有crtl+c按键按下,按下的话就结束下一条命令的处理 by andy
return ; /* if stopped then not repeatable */
}
对从.u_boot_cmd段中找出符合的命令的代码做一下注释
cmd_tbl_t *find_cmd (const char *cmd)
{
cmd_tbl_t *cmdtp;
cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start; /*Init value */
const char *p;
int len;
int n_found = ; /*
* Some commands allow length modifiers (like "cp.b");
* compare command name only until first dot.
*/
len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);//取得命令长度,不包含.后面的字符 by andy for (cmdtp = &__u_boot_cmd_start;//__u_boot_cmd_start在链接文件中定义 by andy
cmdtp != &__u_boot_cmd_end;//__u_boot_cmd_end在链接文件中定义 by andy
cmdtp++) {
if (strncmp (cmd, cmdtp->name, len) == ) {//比较在搜索的段中是否有相同的命令名称 by andy
if (len == strlen (cmdtp->name))
return cmdtp; /* full match *///完全匹配到了 by andy cmdtp_temp = cmdtp; /* abbreviated command ? */
n_found++;
}
}
if (n_found == ) { /* exactly one match *///命令找到了 by andy
return cmdtp_temp;
} return NULL; /* not found or ambiguous command */
}
3、u-boot给内核传递的参数说明
Bootloader与内核的交互式单向的。内核和u-boot约定好将参数放在某个位置,内核去取就行了。而这里规定的地址为0x30000100。具体含义在前面第一点已经介绍。这里只需要知道0x30000100是内核与u-boot交互的数据的首地址。
/* adress of boot parameters */
gd->bd->bi_boot_params = 0x30000100;//与内核交互的数据存放的地址设置 by andy
u-boot与内核交互的数据称为标记列表,位于Setup.h (include\asm-arm) ,它是以ATAG_CORE开始、ATAG_NONE结束。它的结构为:
struct tag {
struct tag_header hdr;//结构体
union {//联合体
struct tag_core core;//开始标记
struct tag_mem32 mem; //内存标记
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;//命令行标记 /*
* Acorn specific
*/
struct tag_acorn acorn; /*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};
tag_header 的结构为
struct tag_header {
u32 size;//标记的大小
u32 tag;//标记的类型
};
主要的标记列表有ATAG_CORE开始标记、ATAG_MEN内存标记、ATAG_CMDLINE命令行标记、ATAG_NONE结束标记
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG) || \
defined (CONFIG_LCD) || \
defined (CONFIG_VFD)
setup_start_tag (bd);//开始标记初始化
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);//内存标记初始化
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);//命令行标记初始化
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd);//结束标记
#endif /* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n"); #ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
//udc_disconnect ();del by andy
}
#endif
ATAG_CORE开始标记初始化
static void setup_start_tag (bd_t *bd)//设置标记atag_core
{
params = (struct tag *) bd->bi_boot_params;//标记的初始化地址为0x30000100 params->hdr.tag = ATAG_CORE; //开始标记的符号
params->hdr.size = tag_size (tag_core); //开始标记的大小 params->u.core.flags = ;
params->u.core.pagesize = ;
params->u.core.rootdev = ; params = tag_next (params);//计算出下一个标记的地址
}
ATAG_MEN内存标记初始化
static void setup_memory_tags (bd_t *bd)
{
int i; for (i = ; i < CONFIG_NR_DRAM_BANKS; i++) {//只有一个SDRAM
params->hdr.tag = ATAG_MEM; //设置内存标记的符号
params->hdr.size = tag_size (tag_mem32);//计算内存标记的大小 params->u.mem.start = bd->bi_dram[i].start;//SDRAM的开始地址
params->u.mem.size = bd->bi_dram[i].size;//SDRAM的结束地址 params = tag_next (params);//计算下一个标记的地址
}
}
ATAG_CMDLINE命令行标记初始化
static void setup_commandline_tag (bd_t *bd, char *commandline)
{
char *p; if (!commandline)//查看命令行存在
return; /* eat leading white space */
for (p = commandline; *p == ' '; p++);//去掉开始的空格符号 /* skip non-existent command lines so the kernel will still
* use its default command line.
*/
if (*p == '\0')//如果去掉空格后没有字符了,那么结束
return; params->hdr.tag = ATAG_CMDLINE;//设置命令行标记符号
params->hdr.size =
(sizeof (struct tag_header) + strlen (p) + + ) >> ;//计算命令行标记的大小 strcpy (params->u.cmdline.cmdline, p);//将命令行拷贝到命令行标记存放处 params = tag_next (params);//计算下一个标记的地址
}
ATAG_NONE结束标记
static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE;//结束标记的标志
params->hdr.size = ; //结束标记的大小为0
}
4、内核启动流程
接着第二条后面的内容分析。运行bootm命令后已经找到了bootm命令的执行函数,接下来进入bootm命令的执行函数do_bootm函数分析,它位于Cmd_bootm.c (common) 文件中。
首先是一些参数的检查以及打印一些内核准备运行的符号
if (argc < ) {//如果参数小于2,从默认地址0x33000000 启动 by andy
addr = load_addr;
} else {
addr = simple_strtoul(argv[], NULL, );//否则取出第二个参数addr = 0x30007FC0
} SHOW_BOOT_PROGRESS ();//空
printf ("## Booting image at %08lx ...\n", addr);//打印内核从哪里启动 by andy
接着往下看是从内核的头64个字节中取出内核的头部到header变量中
memmove (&header, (char *)addr, sizeof(image_header_t));//拷贝内存0x30007FC0 到header,这里的数据是内核提供的,主要用于作比较看内核是否符号启动条件
接着是比较U-BOOT支持的内核与需要启动的内核是否一致
if (ntohl(hdr->ih_magic) != IH_MAGIC) {//如果uboot启动的内核和位于内存的内核不匹配 则不启动内核 by andy
#ifdef __I386__ /* correct image format not implemented yet - fake it */
if (fake_header(hdr, (void*)addr, -) != NULL) {
/* to compensate for the addition below */
addr -= sizeof(image_header_t);
/* turnof verify,
* fake_header() does not fake the data crc
*/
verify = ;
} else
#endif /* __I386__ */
{
puts ("Bad Magic Number\n");
SHOW_BOOT_PROGRESS (-);
return ;
}
}
SHOW_BOOT_PROGRESS ();//空
接下去是进行从内核取出的头部数据的CRC校验
data = (ulong)&header; //取出头部数据的地址
len = sizeof(image_header_t);//取出头部数据的大小 checksum = ntohl(hdr->ih_hcrc);//取出头部数据的crc校验值
hdr->ih_hcrc = ; if (crc32 (, (uchar *)data, len) != checksum) {//如果内核的头数据crc校验出错也不启动内核 by andy
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-);
return ;
}
SHOW_BOOT_PROGRESS ();//空 by andy
CRC校验正确后打印头部信息如下
Image Name: Linux-2.6.22.6
Created: -- :: UTC
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: Bytes = 1.8 MB
Load Address:
Entry Point:
Verifying Checksum ... OK
具体的函数为
/* for multi-file images we need the data part, too */
print_image_hdr ((image_header_t *)addr);//打印内核信息 by andy
print_image_hdr (image_header_t *hdr)
{
#if (CONFIG_COMMANDS & CFG_CMD_DATE) || defined(CONFIG_TIMESTAMP)
time_t timestamp = (time_t)ntohl(hdr->ih_time);
struct rtc_time tm;
#endif printf (" Image Name: %.*s\n", IH_NMLEN, hdr->ih_name);//打印内核名称Linux-2.6.22.6
#if (CONFIG_COMMANDS & CFG_CMD_DATE) || defined(CONFIG_TIMESTAMP)
to_tm (timestamp, &tm);
printf (" Created: %4d-%02d-%02d %2d:%02d:%02d UTC\n",
tm.tm_year, tm.tm_mon, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);//打印创建时间2013-08-23 7:33:38 UTC
#endif /* CFG_CMD_DATE, CONFIG_TIMESTAMP */
puts (" Image Type: "); print_type(hdr);//打印内核类型ARM Linux Kernel Image (uncompressed)
printf ("\n Data Size: %d Bytes = ", ntohl(hdr->ih_size));
print_size (ntohl(hdr->ih_size), "\n");//打印内核大小1848668 Bytes = 1.8 MB
printf (" Load Address: %08x\n"
" Entry Point: %08x\n",
ntohl(hdr->ih_load), ntohl(hdr->ih_ep));//打印装载地址与切入点地址Load Address: 30008000 Entry Point: 30008000 if (hdr->ih_type == IH_TYPE_MULTI) {
int i;
ulong len;
ulong *len_ptr = (ulong *)((ulong)hdr + sizeof(image_header_t)); puts (" Contents:\n");
for (i=; (len = ntohl(*len_ptr)); ++i, ++len_ptr) {
printf (" Image %d: %8ld Bytes = ", i, len);
print_size (len, "\n");
}
}
}
接着是校验内核数据
data = addr + sizeof(image_header_t);//真正的linux内核位于的地方 by andy
len = ntohl(hdr->ih_size);//内核的长度 if (verify) {//如果需要验证 by andy
puts (" Verifying Checksum ... ");
if (crc32 (, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {//crc校验内核数据 by andy
printf ("Bad Data CRC\n");
SHOW_BOOT_PROGRESS (-);
return ;
}
puts ("OK\n");//验证成功 by andy
}
SHOW_BOOT_PROGRESS ();//空 len_ptr = (ulong *)data;//data 为真正的linux内核地址为0x30008000 by andy
接下去是运行do_bootm_linux函数,准备启动内核了
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify);//设置标记列表,然后启动内核
接着分析do_bootm_linux函数,它在Armlinux.c (lib_arm)中定义。前面主要是一些参数的检查,就不列出程序了。只列出一些关键的程序
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");//取得命令行 by andy
#endif theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);//theKernel函数指针hdr->ih_ep为内核切入点的地址,是内核传过来的 by andy /*参数列表初始化*/
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG) || \
defined (CONFIG_LCD) || \
defined (CONFIG_VFD)
setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd);
#endif /* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n"); #ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
//udc_disconnect ();del by andy
}
#endif cleanup_before_linux ();//关闭I-cache与D-cache,清除I-cache与D-cache
theKernel (, bd->bi_arch_number, bd->bi_boot_params);//启动内核bd->bi_arch_number机器类型ID,bd->bi_boot_params为标记列表的开始地址
最终调用theKernel进入内核
u-boot之内核是怎么启动的的更多相关文章
- ARM linux解析之压缩内核zImage的启动过程
ARM linux解析之压缩内核zImage的启动过程 semilog@163.com 首先,我们要知道在zImage的生成过程中,是把arch/arm/boot/compressed/head.s ...
- The Kernel Boot Process.内核引导过程
原文标题:The Kernel Boot Process 原文地址:http://duartes.org/gustavo/blog/ [注:本人水平有限,只好挑一些国外高手的精彩文章翻译一下.一来自己 ...
- STM32F103 ucLinux内核没有完全启动
STM32F103 ucLinux内核没有完全启动 从BOOT跳转到内核后,执行一长段的汇编语言,然后来到startkernel函数,开启C语言之旅. 但是内核输出不正常,如下所示: Linux ve ...
- 如何避免升级 Linux 实例内核后无法启动
如何避免升级 Linux 实例内核后无法启动_系统配置_操作运维 Linux_常见问题_云服务器 ECS-阿里云 https://help.aliyun.com/knowledge_detail/59 ...
- Centos7升级内核后无法启动解决办法
前言 这个问题存在有一段时间了,之前做的centos7的ISO,在进行内核的升级以后就存在这个问题: 系统盘在板载sata口上是可以正常启动新内核并且能识别面板硬盘 系统盘插在面板口上新内核无法启动, ...
- unable to boot the simulator,无法启动模拟器已解决
突然模拟器报错:unable to boot the simulator(无法启动模拟器) 试了好几种解决办法,删除所有的模拟器重启以后再添加,删除钥匙串登陆中的证书,重新安装Xcode都不行 最后通 ...
- Spring boot 整合hive-jdbc导致无法启动的问题
使用Spring boot整合Hive,在启动Spring boot项目时,报出异常: 经过排查,是maven的包冲突引起的,具体做法,排除:jetty-all.hive-shims依赖包.对应的po ...
- arm-linux内核start_kernel之前启动分析(1)-接过bootloader的衣钵
前段时间移植uboot细致研究过uboot启动过程,近期耐不住寂寞.想对kernel下手. Uboot启动过程分析博文连接例如以下: http://blog.csdn.net/skyflying201 ...
- 使用Spring boot整合Hive,在启动Spring boot项目时,报错
使用Spring boot整合Hive,在启动Spring boot项目时,报出异常: java.lang.NoSuchMethodError: org.eclipse.jetty.servlet.S ...
随机推荐
- 如何用java读取properties文件
1.Properties类与Properties配置文件 Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存属性集.不过Properties有特殊的地 ...
- [C语言]数据类型与计算
------------------------------------------------------------------------------------------------- 实际 ...
- 关于PHP Notice: A non well formed numeric value encountered, 你知道多少
---------------------------------------------------------------------------------------------- A non ...
- JQUERY 简单易用的提示框插件
业务开发过程中,为了避免用户的误操作,提示框是必要的,于是琢磨出了下面这个使用,方便的提示框 还要引入遮罩层的样式如下: /*弹出层*/.input{height: 32px;border: 1px ...
- 17.泛型.md
目录 1.Generic概念 2.泛型类 2.1定义泛型类 定义泛型: 注意要点 2.2泛型类的继承 2.3类型通配符 2.4设置类型形参上下限 上限 下限 2.5泛型接口 定义方法 注意要点 3.泛 ...
- 吴裕雄 09-MySQL删除数据表
以下为删除MySQL数据表的通用语法:DROP TABLE table_name; DROP TABLE runoob_tbl; 使用PHP脚本删除数据表PHP使用 mysqli_query 函数来删 ...
- linux下的arm汇编程序
1.gnu 的编译环境搭建 解压编译工具,加入环境变量PATH 2.编译相关命令的使用 编译命令 arm-linux-gcc -g -c -o led.o main.o led.c main.c / ...
- clone()与image和 cloneTo()
Mat image = imread("1.png" ) ; Mat image1 ; Mat image1(image) ;//仅是创建了Mat的头部分,image1与image ...
- sass 的安装 编译 使用
1.使用node 的command 运行命令: gem install sass2.cmd检查是否安装成功 sass -v 如果成功了 可以看见版本信息Sass 3.5.5 ...3. 创建.scss ...
- centos6.8下配置https服务器
centos6.8下配置https服务器 1.1 环境 l 系统环境:内核环境为2.6.32版本 64位的CentOS release 6.8 (Final) [root@localhost ~] ...