1.程序的内存分配
c 语言程序.c 文件经过编译链接后形成编译、链接后形成的二进制映像文件由堆、栈、数据段(只读数据段,未初始化数据段 BSS,已初始化数据段三部分)、代码段组成。
①栈区 (stack):由编译器进行管理,自动分配和释放,存放的是函数调用过 程中的各种参数,局部变量,返回值以及函数返回地址。
②堆区(heap):用于程序动态申请分配和释放空间,malloc 和 free,程序员 申请的空间在使用结束后应该释放,则程序自动收回。
③全局(静态)存储区:分为 DATA(已经初始化)段,BSS(未初始化)段,DATA 段存放已经初始化的全局变量和静态变量; BSS(未初始化)存放未初始化的全局变量和静态变量。 程序运行结束后自动释放,其中 BSS(全部未初始化区)会被系统自动清零。
④文字常量区 :存放常量字符串,程序结束后由系统释放。
⑤程序代码段:存放程序的二进制代码。
2.什么是进程
程序是一组能识别和执行的指令,每一条指令使计算机执行特定的操作。程序是静态的。
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。简单来说进程就是程序的执行过程。进程是动态的。
3.什么是进程标识符
每个进程都有一个唯一标识该进程的非负整数,称作进程标识符。标识符 0 和 1 保留给系统的两个重要进程。
0 是调度进程,把处理机分配给进程使用。
1 是初始化进程,进行系统初始化。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = getpid();
printf("pid = %d",pid);
return 0;
}
4.父进程与子进程
进程A创建进程B,则称A是B的父进程,B是A的子进程。
5.如何查看进程
1.top命令
2.ps命令,可配合grep进行精准查找
ps -aux|grep <字段>
ps -ef|grep <进程名/PID号> //查找父进程
6.创建进程
- 1.fork
函数原型
返回值
负值:创建子进程失败。
0:返回到新创建的子进程。
正整数:返回到父进程或调用者,值为新建子进程的进程ID
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data;
printf("Initial PID: %d\n",getpid());
pid= fork();
if(pid > 0){
while(1){
data++;
printf("Parent process! Return value: %d PID: %d data: %d\n",pid,getpid(),data);
sleep(2);
if(data == 5){
exit(0);
}
}
}else if(pid == 0){
while(1){
data++;
printf("Subprocess! Return value: %d PID: %d data: %d\n",pid,getpid(),data);
sleep(2);
if(data == 5){
exit(0);
}
}
}
return 0;
}
- 2.vfork
与fork的区别:vfork直接使用父进程的存储空间,不进行拷贝。同时vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。
函数原型:
返回值:
-1:创建子进程失败。
0:返回到新创建的子进程。
正整数:返回到父进程或调用者,值为新建子进程的进程ID
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data;
printf("Initial PID: %d\n",getpid());
pid= vfork();
if(pid > 0){
while(1){
data++;
printf("Parent process! Return value: %d PID: %d data: %d\n",pid,getpid(),data);
sleep(2);
}
}else if(pid == 0){
while(1){
data++;
printf("Subprocess! Return value: %d PID: %d data: %d\n",pid,getpid(),data);
sleep(2);
if(data == 5){
exit(0);
}
}
}
return 0;
}
7.进程退出
-
正常退出
1.main函数调用return 2.进程调用exit(),标准C库 3.进程调用_exit()或_Exit(),属于系统调用 4.进程最后一个线程返回 5.最后一个线程调用pthread_exit()
-
异常退出
1.调用abort 2.进程收到某些信号,例如CTRL + C 3.最后一个线程对取消(cancellation)请求做出反应
不管进程如何退出,最后都会释放它所使用的存储器等。
8.等待子进程退出
子进程退出状态不被收集,则变为僵尸进程。
- 1.wait函数
函数原型:
参数说明:
保存进程的退出状态 (正常终止→退出值;异常终止→终止信号)。借助宏函数来进一步判断进程终止的具体原因。
宏函数可分为如下三组:
①WIFEXITED(status) 为非0 → 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
②WIFSIGNALED(status) 为非0 → 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
③ WIFSTOPPED(status) 为非0 → 进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
返回值:
成功:清理掉的子进程ID
失败:-1 (没有子进程)
功能:
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data;
int status;
printf("Initial PID: %d\n",getpid());
pid= fork();
if(pid > 0){
wait(&status);
printf("status = %d\n",WEXITSTATUS(status));
while(1){
printf("Parent process! Return value: %d PID: %d data: %d\n",pid,getpid(),data);
sleep(2);
}
}else if(pid == 0){
while(1){
data++;
printf("Subprocess! Return value: %d PID: %d data: %d\n",pid,getpid(),data);
sleep(2);
if(data == 5){
exit(5);
}
}
}
return 0;
}
- 2.waitpid函数
函数原型:
使用waitpid函数父进程不会阻塞,但子进程仍然会成为僵尸进程。
9.孤儿进程
父进程不等待子进程退出,在子进程之前就结束了,此时子进程叫做孤儿进程。
linux避免系统存在过多的孤儿进程,init进程(pid = 1)收留孤儿进程,变成孤儿进程的父进程。
10.exec族函数
在进程中可调用exec族函数执行一个可执行文件
注意:调用成功后不会再执行后续代码
函数原型:
返回值:
调用成功即执行新的程序,不返回。只有失败才返回,错误值-1。
可以直接调用perror()判断失败原因。
参数说明:
path:文件路径
接下来的参数代表执行该文件时传递过去的argv(0),argv[1], …, 最后一个参数必须用空指针(NULL)作结束
规律:
l (list): 命令行参数列表,并非形参列表。
p (path): 搜素file时使用path变量。
v (vector) : 使用命令行参数数组,类似list。
e (environment): 使用环境变量数组,设置新加载程序运行的环境变量,不使用进程原有的环境变量。
例子:
- 1.execl
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("start!\n");
if(execl("./test","test","ABC 123",NULL) == -1){
perror("error:");
}
printf("end!\n");
return 0;
}
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("start!\n");
if(execl("/bin/date","date",NULL) == -1){
perror("error:");
}
printf("end!\n");
return 0;
}
- 2.execlp
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("start!\n");
if(execlp("ps","ps",NULL) == -1){
perror("error:");
}
printf("end!\n");
return 0;
}
- 3.execvp
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("start!\n");
char *argv[] = {"ls",NULL};
if(execvp("ls",argv) == -1){
perror("error:");
}
printf("end!\n");
return 0;
}
11.system函数
功能与exec族函数相同,但调用完成后会继续执行后续代码。
函数原型:
参数说明:
command:包含被请求变量名称的 C 字符串。
返回值:
发生错误,返回值为 -1,否则返回命令的状态。
例子:
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("start!\n");
system("date");
if(system("ls") == -1){
perror("");
}
printf("end!\n");
return 0;
}
12.popen函数
与exec族、system函数相比,可以得到指令的执行结果,存储在字符串中,并选择是否输出。
函数原型:
参数说明:
command:使用的命令
type:如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。
返回值:
标准I/O流
例子:
#include <stdio.h>
#include <unistd.h>
int main()
{
FILE *fp;
char buf[1024];
fp = popen("ps","r");
fread(buf,1,1024,fp);
printf("Operation results:\n%s\n",buf);
return 0;
}