什么是环境变量
http://t.csdnimg.cn/nPrMu
进程:是程序执行的一次执行过程,是动态,涉及到资源分配,包含创建、调度、执行
程序:存放在磁盘空间上的一个二进制文件,是指令集合,是静态的,没有执行的过程
进程内存三个段:正文、数据、堆栈
进程分类:交互进程、守护进程、批处理进程
进程状态:
进程是资源分配的最小单位,线程是cpu调度的最小单位
创建进程:pid_t pid = fork();
退出进程:exit(0) 刷新缓存区
_exit(0) 不刷新缓存区
回收进程资源:
wait(NULL)父进程使用
一、进程概念
程序:编译好的可执行二进制文件,存放在磁盘上的指令和数据的有效集合,是静态的,没有任何执行的概念。a.out
进程:独立的可调度的任务,是执行一个程序所分配资源的总称,是程序的一次执行过程,是动态的,包括创建、调度、执行和消亡。./a.out按下回车的那一刻开始。
二、进程和程序的区别
程序:编译好的可执行的二进制文件
存放在磁盘上的指令和数据的有序集合(文件)
程序是静态的,没有任何执行的概念
进程:一个独立的可调度的任务
执行一个程序所分配的资源的总称
进程是程序的一次执行过程
进程是动态的,包括创建、调度、执行和消亡
三、进程包含三个段:
- 系统会为每一个进程分配0-4g的虚拟空间,0-3g(用户空间)是每个进程所独有的,3g-4g(内核空间)是所有进程共有的。
2.CPU调度进程时会给进程分配时间片(几毫秒~十几毫秒),当时间片用完后,cpu再进行其他进程的调度,实现进程的轮转,从而实现多任务的操作
1)“数据段”存放的是全局变量、常数,static修饰的变量等。
2)“正文段”存放的是程序中的代码
3)“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量,malloc开辟的内存空间。
3.进程的内存管理(如内存管理图)
1. 每个进程都分配4G虚拟内存空间
2. 0G~3G 是用户空间-属于进程私有空间, 3G~4G 是内核空间-所有进程公用空间
3. 3G~4G 是所有进程共享的
(mmu:内存管理单元,虚拟机只有mmu能操作物理地址,管理用户使用的物理地址其他用户将不能在获取使用)
代码段、数据段、bss段、只读数据段、堆段、栈段(都占用自己的虚拟地址并映射相应的物理地址)
4.进程是程序执行和资源管理的最小单位。
包含的的资源有:物理内存、文件描述符、虚拟地址 0G~4G、CPU时间片、进程号 (PID唯一标识一个进程)
五、进程的类型:
1)交互进程:交互进程既可以在前台运行,也可以在后台运行。该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类进程会立刻响应。
2)批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行
3)守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
六、进程的运行状态:
1) 运行态:此时进程或者正在运行,或者准备运行。
2) 等待态:此时进程在等待一个事件的发生或某种系统资源。
可中断:处在这种状态下的进程可以被信号中断,接收到信号或被显示地唤醒呼叫,唤醒之后,进程将转变为运行态。
不可中断:它不会处理信号,只有在它所等待的事件发生时,进程才被显示的唤醒
3)停止态:此时进程被中止。
4)死亡态:这是一个已终止的进程,但还在进程向量数组中占有一个task_struct结构。
5)僵尸态(TASK_ZOMBIE):Z
当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生
D uninterruptible sleep (usually IO)(不可中断的等待态)
R running or runnable (on run queue)(运行态)
S interruptible sleep (可中断的等待态)
T stopped, either by a job control signal or because it is
being traced.(停止态)
X dead (should never be seen)(死亡态)
Z defunct ("zombie") process, terminated but not reaped by its
parent.(僵尸态)
For BSD formats and when the stat keyword is used, additional
characters may be displayed:(对于BSD格式,当使用stat关键字时,可能会显示额外的字符:)
< high-priority (not nice to other users)(高优先级)
N low-priority (nice to other users)(低优先级)
s is a session leader(会话组组长)
多个进程可以组成一个组,多个组可以组成一个会话。
多个会话可以组成一个会话组。
l is multi-threaded (线程)
+ is in the foreground process group.(前台)
空:表示后台
七、进程状态切换图
进程创建后,进程进入就绪态,当CPU调度到此进程时进入运行态,当时间片用完时,此进程会进入就绪态,如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞态,完成IO操作(阻塞结束)后又可进入就绪态,等待CPU的调度,当进程运行结束即进入结束态。
Ctrl+z:将前台运行的进程暂停同时放到后台
bg 数字(这里的数字为你按Ctrl+Z的时候前面中括号里面的数字):将后台暂停的进程在后台跑起来。
fg 数字(这里的数字为你按Ctrl+Z的时候前面中括号里面的数字) :将后台运行的进程拉到前台运行
jobs:查看后台所有的进程
-
八、创建进程函数:
1、fork 创建一个子进程
#include <unistd.h>
pid_t fork(void);
功能:创建一个子进程
参数:无
返回值:
成功:在父进程中:返回子进程的进程号(PID) >0
在子进程中:返回值为0
失败:-1并设置errno,子进程创建失败
父进程 PPID 子进程 PID
子进程PID可以是除1(ROOT进程)以外任何PID号
父子进程执行顺序是随机的
#include<stdio.h>
#include<unistd.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork err");
return -1;
}
else if(pid == 0)
{
printf("this is child.\n");
}
else
{
printf("this is father.\n");
}
return 0;
}
2、fork创建子进程的特点:
1.fork创建一个子进程,父进程返回子进程的pid,子进程中返回0.
如果父进程退出,子进程没有退出,子进程会变成孤儿进程被init进程收养。
如果子进程退出,父进程没有退出,父进程没有回收子进程资源,子进程会变成僵尸进程。
2.fork创建的子进程几乎拷贝了父进程所有的内容(三个段:堆栈、数据、代码,没有被拷贝的内容:pid、 ppid、cpu等),fork之前的代码被复制并不会被执行,fork之后的代码被复制并执行。
3.fork创建进程一旦成功,进程之间的空间就相会独立。各自分配0-4G的虚拟内存空间。
4.fork创建进程之前打开的文件可以通过复制拿到同一个文件描述符操作同一个文件(同一个文件指针)。
写时拷贝
由于之前的fork完整地拷贝了父进程的整个地址空间,因此执行速度是比较慢的。为了提高效率,Unix系统设计者创建了现代unix版的fork。现代unix版的fork也创建新进程,但不产生父进程的副本。它通过允许父子进程可访问相同物理内存从而伪装了对进程地址空间的真实拷贝,当子进程需要改变内存中数据时才拷贝父进程。这就是著名的"写操作时拷贝"(copy-on-write)技术
特点验证:(ps -ef查看父子进程)
1)子进程几乎拷贝了父进程的全部内容。包括代码、数据、缓冲区、系统数据段中的pc值、栈中的数据、父进程中打开的文件等;但它们的PID、PPID是不同的。
2)父子进程有独立的地址空间,互不影响;当在相应的进程中改变全局变量、静态变量,都互不影响.
3)fork之前代码会被复制,但是不会重新执行一遍;fork之后的代码,会被复制并且分别执行一遍。
4)若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程。
init进程PPID为1(因为显示孤儿进程所以存在图像处理所以孤儿进程父进程PPID不为1)
5)若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程(要避免僵尸进程产生)
6)fork父子进程哪个先执行没有顺序,vfork:先执行完子进程再执行父进程
7)fork创建进程之前打开的文件可以通过复制拿到同一个文件描述符操作同一个文件(同一个文件指针)。也能操作文件内同一个指针。
-
九、进程退出函数 exit _exit
#include <stdlib.h>
void exit(int status);
功能:退出进程
参数:status:退出进程的状态,exit(0):正常退出,非零状态码表示异常退出
返回值:无
一旦调用了 exit() 函数,程序将立即终止,之后的代码将不再执行。
#include <unistd.h>
void _exit(int status);
功能:退出进程
参数:status:退出进程的状态
返回值:无
exit退出进程会刷新缓存区,_exit退出进程不刷新缓存区
十、回收进程资源 wait waitpid
1、wait
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:阻塞等待子进程退出回收子进程资源 (父进程中使用)
阻塞父进程,等待子进程结束。
参数:
①status作为一个整形值,用来保存子进程退出时的状态;
②如果status为NULL,表示忽略子进程退出时的状态;
③如果status不为空,wait函数会将子进程退出的状态存入status中,另外,子进程退出时的状态可以通过linux中的特定的宏(macro)来进一步测定退出状态
返回值:
成功:退出进程的进程号
失败:-1
练习: 1. 通过父子进程完成文件io对文件的拷贝cp,父进程从文件开始到文件的一半开始拷贝,子进程从文件的一半到文件末尾。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <strings.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
if (argc < 2)
{
printf("input err");
return -1;
}
int fd = open(argv[1], O_RDONLY);
if (fd < 0)
{
perror("open err");
}
int fd2 = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0777);
if (fd < 0)
{
perror("open err");
}
char buf[32] = "";
int ret;
int num = lseek(fd, 0, SEEK_END);
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0) //子进程复制后一半
{
lseek(fd,num/2,SEEK_SET);
lseek(fd2,num/2,SEEK_SET);
while (ret = read(fd, buf, 1))
{
write(fd2, buf, ret);
}
exit(-1);
}
else //父进程
{
wait(NULL);//阻塞等待子进程退出回收子进程资源
lseek(fd,0,SEEK_SET);
lseek(fd2,0,SEEK_SET);
int n=num/2;
// for(int i=num/2;i>0;i--)//读一个写一个
// {
// read(fd, buf, 1);
// write(fd2, buf, 1);
// }
while (n)
{
if(n>sizeof(buf))
{
ret = read(fd,buf,sizeof(buf));
}
else
{
ret = read(fd,buf,n);
}
write(fd2,buf,ret);
n=n-ret;
}
exit(0);
}
close(fd);
close(fd2);
return 0;
}
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
功能:回收子进程资源
参数:
pid:>0 指定子进程进程号
=-1 任意子进程
=0 等待其组ID等于调用进程的组ID的任一子进程
<-1 等待其组ID等于pid的绝对值的任一子进程
status:子进程退出状态
options:0:阻塞
WNOHANG:非阻塞
返回值:正常:结束的子进程的进程号
当使用选项WNOHANG且没有子进程结束时:0
出错:-1
二者关系:wait(NULL);==waitpid(-1,NULL,0);
子进程状态发生改变,会给父进程发送一个信号叫SIGCHLD信号
十一、获取进程pid和父进程ppid(getpid,getppid)
永远会成功的函数:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
功能:获取当前进程的进程号
#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);
功能:获取当前进程的父进程号
十二、交互进程 bg fg
进程命令:
ps --》
ps -aux 查看进程相关信息
ps -ef 查看进程的父进程
ps -ajx 查看进程父进程id、组id、会话id、会话组id
多个进程组成一个组
多个组组成一个会话
多个会话组成一个会话组
top 动态显示进程状态
nice / renice
kill --》kill -l 查看有哪些信号
kill -num PID
ctrl +z 暂停进程
前后台进程切换的命令:
查看终端有那些作业:用jobs命令
bg 将挂起的进程在后台执行
bg 编号 [1]作业号
bg $作业号
fg 把后台运行的进程放到前台运行
fg 编号
./a.out & 将a.out在后台运行起来
给后台的作业发送信号:
kill -num %作业号
Ctrl+z:将前台运行的进程暂停同时放到后台
bg 数字(这里的数字为你按Ctrl+Z的时候前面中括号里面的数字):将后台暂停的进程在后台跑起来。
fg 数字(这里的数字为你按Ctrl+Z的时候前面中括号里面的数字) :将后台运行的进程拉到前台运行
jobs:查看后台所有的进程
十三、守护进程创建步骤
- 特点
守护进程是后台进程;生命周期比较长,从系统启动时开启,系统关闭时结束;它是脱离控制终端且周期执行的进程。
2.步骤
1) 创建子进程,父进程退出
让子进程变成孤儿进程,成为后台进程;fork()
2) 在子进程中创建新会话
让子进程成为会话组组长,为了让子进程完全脱离终端;setsid()
【会话组(班长)--》进程组(组长)】
3) 改变进程运行路径为根目录
原因进程运行的路径不能被删除或卸载;chdir("/")
4) 重设文件权限掩码
目的:增大进程创建文件时权限,提高灵活性;umask(0)
【mode &(~umask)想要相与之后的结果最大,最大为自己设置的mode值,任意一个数与全为1的数相与,能得到本身,所以就要求(~umask)最大777,那(umask)最小000】
5) 关闭文件描述符
将不需要的文件关闭;close()
1.创建一个子进程,让父进程退出
目的:让子进程变成孤儿进程,被Init 进程收养,变成后台进程
fork();
2.设置进程为会话组组长
多个进程组成一个组,多个组组成一个会话,多个会话组成一个会话组
目的:脱离终端
setsid();
3.修改程序的执行路径到根目录
chdir();
4.重设权限掩码 umask 0022
umask();
5.关闭文件(释放多余文件资源)
close();
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
setsid(); //设置会话组组长
chdir("/");//修改程序的执行路径到根目录
umask(0022); //0777 &(0022)==0755,重设权限掩码 umask
for (int i = 0; i < 3; i++)
{
close(i);
}
int fd = open("./a.txt", O_WRONLY | O_CREAT | O_TRUNC, 0777);
while (1)
{
sleep(1);
write(fd, "hello world\n", 12);
}
}
else
{
exit(0);
}
return 0;
}
十四、exec函数族
在一个进程中执行另外的进程
man 3 exec
1.概念:
exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。
2.何时使用exec?
1)当进程认为自己不能再为系统和用户做出任何贡献了时就可以调用exec函数,让自己执行新的程序
2)如果某个进程想同时执行另一个程序,它就可以调用fork函数创建子进程,然后在子进程中调用任何一个exec函数。这样看起来就好像通过执行应用程序而产生了一个新进程一样
#include <unistd.h>
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, 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[]);
l : list列表,参数要以列表形式,展现出来。
p : path路径,从系统环境变量中去找可执行文件
v : 向量数组,将列表的参数装到数组中去。
e : 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环 境。envp也是一个以NULL结尾的字符串数组指针
3.使用区别:
1)可执行文件查找方式
无p:指定完整的文件目录路径,
有p:只给出文件名,系统会自动从环境变量“$PATH”所包含的路径中进行查找。
2)参数表传递方式
有l:表示逐个列举的方式;
有v:表示将所有参数构造成指针数组传递,其语法为char *const argv[]
3)环境变量的使用
exec函数族可以默认使用系统的环境变量,也可以传入指定的环境变量。
这里,以“e”(Enviromen)结尾的两个函数execle、execve就可以在envp[]中传递
当前进程所使用的环境变量,一般为NULL即可