stdin 标准输入 0
stdout 标准输出 1
stderr 标准错误 2
进程
1.程序
数据结构+算法=程序
程序是存放在磁盘中的可执行文件。
程序是静态的(指令的集合)
2.进程
运行起来的程序称为进程
进程是独立的活动单位,是程序执行和资源管理的最小单位
一个程序可以对应多个进程(即多次打开运行同一个软件,例如QQ飞车开小号刷任务)
每个进程从启动开始就都有4G虚拟内存
3.进程号
每个Linux进程都有一个唯一的数字标识符,称为进程ID(PID)
本质上就是一个非负的整数
实例:
ps -ef //以全格式查看系统中所有的进程
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Feb04 ? 00:00:02 /sbin/init
UID 用户名
PID 进程号
PPID 父进程的进程号
C 占用CPU的百分比
STIME 启动时间
TTY 终端号(?表示没有控件终端)
TIME 消耗CPU的时间
CMD 进程名称
4.父子进程
如果进程A启动进程B,那么A称为父进程,B称为子进程
Linux进程的启动顺序:
进程0(系统进程)负责启动进程1(init进程)和2,其他所有的进程都1和2直接
或间接启动,从而形成树形结构。
获取父(PPID)子(PID)进程的进程号:
#include <unistd.h>
子进程: pid_t getpid(void);
列如:printf("child process %d is running \n",getpid());
//打印子进程的PID,表示进入子进程
父进程: pid_t getppid(void);
列如:printf("parent process %d is running \n",getppid());
//打印父进程的PPID,表示进入父进程
./a.out & 把a.out放到后台运行,不占用当前终端
5. Linux进程地址空间布局
程序的结构与进程的结构
一个可执行程序包含三部分,用 size a.out 命令查看
text data bss dec hex filename
1172 284 4 1460 5b4 a.out
text 代码段:主要存放指令
data 数据段:已初始化的全局或静态的变量
bss BSS段:未初始化的全局或静态变量,BSS段会在main()执行之前自动清0
在执行程序时,系统会在内核中创建一个进程,
为这个进程申请PCB(task_struct),用于管理整个进程的资源
其中,mm_struct成员用来管理与当前进程相关的所有的内存资源
32位平台下,【一个进程拥有4G虚拟地址空间】
1.代码段、数据段、BSS段,这些直接从磁盘copy到内存
其它代码段从0x08048000地址开始
2.堆 通常在堆中进行动态内存分配 malloc系列
3.栈 保存局部变量(包括函数参数),分配和回收都自动进行
4.高地址1G空间供内核映射处理,用户程序不能直接访问
readelf -a a.out 指令查看
6. Linux虚拟内存
在linux中,内存地址都是虚拟内存地址,不是物理内存
? 每个【进程一启动,就先天赋予0-4G的虚拟内存】,虚拟内存本质上是一个整数,
这个整数通过内存映射对应一个物理内存的地址,但先天不对应任何的物理内存。
虚拟内存自身存储不了任何数据,只有内存映射后才能存储数据,否则引发段错误
0-3G是给用户使用的,称为用户空间
3-4G是给内核使用的,称为内核空间
用户空间的程序不能直接访问内核空间,但可以通过内核提供的系统函数进入内核
空间。
进程使用步骤及函数:
1.创建一个新进程
pid_t fork(void);
返回值:成功:给父进程返回创建的子进程的PID
给子进程返回0
失败:返回-1,errno被设置
例如:
pid_t pid = fork();
if(pid < 0)
{
//创建失败
perror("fork");
exit(-1);
}
else if(pid == 0)
{
//子进程中
}
else if(pid > 0)
{
//父进程中
}
说明:
1.fork创建后,父子进程谁先执行不确定的,根据调度算法的不同,顺序可能有所不同。
Linux自从2.6.x后,默认先执行父进程,但不能假设一直是这样的。
2.使用fork创建子进程后,父子进程的执行方式:
a.对于fork之前的代码,由父进程执行一次
b.对于fork之后的代码,由父子进程各执行一次,即两次
c.fork函数的返回值也是由父子进程各自返回一次
3.fork之后,父子进程同时运行,
如果子进程先结束,子进程会给父进程发一个信号,父进程负责回收子进程的资源。
如果父进程先结束,子进程会变成孤儿进程,会认进程1(init)做新的父进程
init进程称为孤儿院。
如果子进程在发送信号时,出现了问题或父进程没有及时处理信号,子进程就会成为僵尸进程。
2.终止进程
#include <stdlib.h>
void exit(int status);
不会立即终止进程,可以调用atexit()注册过的函数之后,再结束
要检查文件的打开情况,把文件的缓冲区中的内容写回文件,即清理IO缓冲
例如: exit(0);
正常结束:
1.在main函数中,执行return 0;
2.调用exit(0)/_exit(0)
非正常结束:
在外力干涉下挂掉的,操作系统干掉的
/****执行注册过的函数之后,再结束进程***/
void func(void)
{
printf("this is exit function\n");
}
int main()
{
atexit(func);
printf("this is main process\n");
exit(0);
}
/*************
输出:this is main process
this is exit function
*************/
/**********立即结束进程********/
void func(void)
{
printf("this is exit function\n");
}
int main()
{
atexit(func);
printf("this is main process\n");
_exit(0);
}
/************
输出:this is main process
************/
int atexit(void (*function)(void));
功能:用于注册参数指定的函数,该函数会在进程结束时被调用
返回值:成功返回 0
失败返回非0
在main函数中,return与exit功能一样。
return 用于函数返回
exit 用于退出进程
3.等待某进程先结束
wait(&status)与waitpid(-1, &status, 0);等价
#include <sys/types.h>
#include <sys/wait.h>
方法一: pid_t wait(int *status);
用于挂起当前正在运行的进程,直到有一个子进程终止
返回值:成功返回终止子进程的PID,失败返回-1
当参数不空时,会将获取到的状态信息存放到参数指定的空间,
通过以下宏来获取相应信息
WIFEXITED(status)--当正常结束时,返回true
WEXITSTATUS(status)--获取子进程的退出状态
例如:
int status;
pid_t res=wait(NULL);
if(res == -1)
{
perror("wait");
exit(-1);
}
if(WIFEXITED(status))//normal exit
//当正常结束时(即子进程结束),返回true
{
printf("child pid = %d,normal exit,exit status = %d\n",res,WEXITSTATUS(status));
}
//挂起当前正在运行的进程,等待任意进程结束,并打印等待的结果【结束的进程的进程号或-1】
//若没有进程结束,则一直挂起
方法二:pid_t waitpid(pid_t pid, int *status, int options);
用于按照【指定的方式】等待【指定的进程】状态发生改变
pid:等待的进程号
pid < -1 等待进程组ID为pid的绝对值的任意子进程(了解)
pid == -1 等待任意子进程的结束(掌握)
pid == 0 等待和当前进程组在同一个进程组的任意子进程(了解)
pid > 0 等待进程号为pid的进程(掌握)
*status:终止状态所存储的内存单元
如果不关心终止状态,可以将 statloc 参数设置为NULL。
一般都设置为NULL;
当&status不为 NULL 时,会将获取到的状态信息存放到参数指定的空间,
通过以下宏来获取相应信息
WIFEXITED(status)--当正常结束时,返回true
WEXITSTATUS(status)--获取子进程的退出状态
当&status为 NULL 时,【不关心终止状态】
options:等待的方式
0 阻塞
WNOHANG 如果没有子进程结束,立即返回
4.exec函数族
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
exec函数说明:
exec函数提供一个【在进程中启动另一个程序执行】的方法
它可以【根据指定的文件或路径】【找到可执行文件】,并用它来【替换原调用进程的代码段、数据段、堆、栈】
在【执行完成后】,原调用进程的内容,【除了进程PID号,其它全部被新的进程替换】。
list
ls -l a.txt NULL
vector
char *const argv[] = {"ls","-l","a.txt",NULL};
exec函数族:
前4位 统一为exec
第5位 l 参数传递以列表的方式,逐个列举 execl execlp execle
v 参数传递以数组的形式 execv execvp execvpe
第6位 e 可传递环境变量给新进程 execle execvpe
p 可执行文件查找方式为文件名 execlp execvp
参数必须以NULL表示结束
实例:
#include<stdio.h> #include<unistd.h> int main() { printf("hello world\n"); execl("/bin/ls","ls","-l","a.out",NULL); printf("hello world 2\n"); return 0; } /************ 输出结果: hello world -rwxrwxrwx 1 root root 7330 Feb 10 20:36 a.out ************/