当内核执行C程序时,(使用一个exec函数),在调用main函数之前县调用一个特殊启动历程(通常用汇编语言编写,就是运行库)。可执行程序文件将此启动例程指定为程序的起始地址。启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用main函数做好安排。
环境表:每个程序都会接受到一张环境表。历史上main函数曾经有三个参数,第三个参数就是环境表的地址。在exec的四个函数中有参数用来传递环境表。
进程地址空间:内核除了管理本身的内存外还必须管理用户空间中进程的内存,我们称这个内存为进程地址空间。
fork:fork产生的进程与子进程的正文代码段是共享的,但是其他的堆栈空闲区数据段是不共享的,子进程用的是父进程的一个副本(采用的是写时复制技术,进程地址空间部分相同)。
vfork:vfork在调用exec之前与父进程是完全共享的,包括正文段、堆栈以及数据段等。
exec:调用exec并不创建新进程,exec只是用一个全新的程序替换了当前进程的正文、数据、堆和栈。此时这个进程与父进程在正文、数据段、堆栈上已经完全不相关(即进程地址空间完全不同了)。
strcpy、memcpy和sprintf三个函数主要区别:
strcpy只能够执行字符串的复制。
memcpy能够执行内存上任意数据的复制。
sprintf主要用于格式化复制
sprintf 可以进行额外的格式化
strcpy 会复制直到出现 '' 为止,可能溢出
strncpy 会复制一个以 '' 结束的字符串,但是如果字符串长度超过指定数量则被截断,但结果可能不包含 '' 表示结束
memcpy 只负责复制指定数量的 bytes,不处理 '' 的情况
memmove 在 memcpy 的基础上对 overlap 的情况进行了处理。
这些函数的区别在于 实现功能 以及 操作对象 不同。
strcpy 函数操作的对象是 字符串,完成 从 源字符串 到 目的字符串 的 拷贝 功能。
snprintf 函数操作的对象 不限于字符串:虽然目的对象是字符串,但是源对象可以是字符 串、也可以是任意基本类型的数据。这个函数主要用来实现 (字符串或基本数据类型)向 字符串 的转换 功能。如果源对象是字符串,并且指定 %s 格式符,也可实现字符串拷贝功能。
memcpy 函数顾名思义就是 内存拷贝,实现 将一个 内存块 的内容复制到另一个 内存块 这一功能。内存块由其首地址以及长度确定。程序中出现的实体对象,不论是什么类型,其最终表现就是在内存中占据一席之地(一个内存区间或块)。因 此,memcpy 的操作对象不局限于某一类数据类型,或者说可 适用于任意数据类型,只要能给出 对象的起始地址和内存长度信息、并且对象具有可操作性即可。鉴于 memcpy 函数等长拷贝的特点以及数据类型代表的物理意义,memcpy 函数通常限于同种类型数据或对象之间的拷贝,其中当然也包括字符串拷贝以及基本数据类型的拷贝。
对于字符串拷贝来说,用上述三个函数都可以实现,但是其实现的效率和使用的方便程度不同:
- strcpy 无疑是最合适的选择:效率高且调用方便。
- snprintf 要额外指定格式符并且进行格式转化,麻烦且效率不高。
- memcpy 虽然高效,但是需要额外提供拷贝的内存长度这一参数,易错且使用不便;并且如果长度指定过大的话(最优长度是源字符串长度 + 1),还会带来性能的下降。其实 strcpy 函数一般是在内部调用 memcpy 函数或者用汇编直接实现的,以达到高效的目的。因此,使用 memcpy 和 strcpy 拷贝字符串在性能上应该没有什么大的差别。
对于非字符串类型的数据的复制来说,strcpy 和 snprintf 一般就无能为力了,可是对 memcpy 却没有什么影响。但是,对于基本数据类型来说,尽管可以用 memcpy 进行拷贝,由于有赋值运算符可以方便且高效地进行同种或兼容类型的数据之间的拷贝,所以这种情况下 memcpy 几乎不被使用。memcpy 的长处是用来实现(通常是内部实现居多)对结构或者数组的拷贝,其目的是或者高效,或者使用方便,甚或两者兼有。
strcpy和memcpy功能上也有些差别:
比如:
const char *str1="abc\0def";
char str2[7];
首先用strcpy实现:
strcpy(str2,str1)
得到结果:str2="abc";也就是说,strcpy是以'\0'为结束标志的。
再用memcpy实现:
memset(str2,7);
memcpy(str2,str1,7);
得到结果:str2="abc\0def";
也就是说,memcpy是对内存区域的复制。当然,不仅能够复制字符串数组,而且能够复制整型数组等其他数组。
fork出来的两个不同的进程运行在不同的地址空间,对全局变量的访问方式采用的是写时复制技术,所以一个进程A对全局变量a改写之后,进程B是不可见的。
但是这两个进程读写文件的时候就需要加fcntl文件锁对文件进行互斥操作,因为对文件的修改时彼此可见的。
线程对全局变量的访问是彼此可见的所以需要使用互斥操作,就像内核中的全局变量的访问就需要进行互斥操作。
当文件A想要访问文件B的static全局变量的时候,可以在文件B中用一个函数封装这个全局变量,在文件A中来调用这个函数。