1.linux进程
进程:是操作系统资源管理的最小单位
进程是动态的,是程序的一次执行过程,而程序是静态的,进程是运行中的程序,程序只是保存在硬盘上的一些可执行的代码而已,只有当程序转换为进程后,操作系统才为其分配内存单元。
2.进程标识
在操作系统中每个进程都是通过唯一的进程ID标识的。进程ID是一个非负数,每个进程除了进程ID外还有一些其他标识,它们可以通过相应的函数获得。
3.用户ID和组ID:
实际用户ID(uid):标识运行该进程的用户
有效用户ID(euid):标识以什么身份来运行进程,eg:某普通用户A以root身份运行A程序,则这个程序的uid就是A用户的ID,而有效用户ID是root用户的ID。
实际组ID(gid):它是实际用户所属组的组ID。
有效组ID(egid):有效用户所属组的组ID。
4.linux进程的结构
linux中进程由代码段、数据段、堆栈段3部分组成。代码段存放程序的可执行代码。数据段存放程序的全局变量、常量、静态变量。堆栈段中的堆用于存放动态分配(malloc)的内存变量,栈用于函数调用,存放着函数的参数,函数内部定义的局部变量。
5.进程状态
- 运行状态(R):程序正在运行
- 可中断等待状态(S):进程正在等待某个事件完成(如数据到达)。等待过程可被信号或定时器唤醒。
- 不可中断等待状态(D):进程等待某个事件完成,在等待过程中不可以被信号或定时器唤醒,必须等待直到等待的事件发生。
- 僵死状态(Z):进程已终止,但进程描述符依然存在,直到父进程调用wait()函数后释放。
- 停止状态(T):进程因为收到SIGSTOP、SIGSTP、SIGTOU、SIGTIN信号后停止运行或者该进程正在被跟踪(调试状态)。
6.进程控制
linux进程控制包括创建进程、执行新程序、退出进程以及改变进程的优先级,通常通过一系列的系统调用来控制进程。主要系统调用如下:
- fork:用于创建一个新进程
- exit:用于终止进程
- exec:用于执行一个应用程序
- wait:将父进程挂起,等待子进程终止
- getpid:获取当前进程的进程ID
- nice:改变进程的优先级
7.进程的内存映像
linux下程序转化成进程
linux下C程序的生成分为4个阶段:预编译、编译、汇编、链接。程序转化为进程通常要经过以下步骤:
- 内核将程序读入内存,为程序分配内存空间
- 内核为该进程分配进程标示符(PID)和其他所需资源
- 内核为进程保存PID及相应的状态信息,把进程放到运行队列中等待执行。程序转化为进程之后就可以被操作系统的调度程序执行了。
进程的内存映像:进程的内存映像是指内核在内存中如何存放可执行程序文件
linux程序影像从内存的低地址到高地址依次如下:
- 代码段:即二进制机器代码,代码段只读,可被多个进程共享。
- 数据段:存储已被初始化的变量,包括全局变量和已被初始化的静态变量。
- 未初始化数据段:存储未被初始化的静态变量。
- 堆:用于存放数据运行中动态分配的变量
- 栈:用于函数待用,保存函数的返回地址、函数的参数、函数内部定义的局部变量
8.进程的创建
创建进程有两种方式,一是由操作系统创建,二是由父进程创建。操作系统创建的进程,它们之间是平等的,一般不存在资源继承关系。而对于父进程创建的进程(子进程),它和父进程存在隶属关系。
fork函数
#include <unistd.h>
pid_t fork(void);
fork函数有两个返回值,fork函数调用成功之后当前进程就已经分为两个进程,即每个进程有一个返回值,父进程的返回值是子进程的ID,子进程的返回值是0,若fork函数执行失败,则只有一个返回值-1,通过不同的返回值可以区分父子进程。
在fork之后父子进程谁先执行是不确定的,取决于内核使用的调度算法
fork在创建进程失败的原因通常是父进程拥有的子进程的个数超过了规定的限制(errno值为EAGAIN),或是可供使用的内存不足(errno值为ENOMEN)。
子进程会继承父进程的很多属性主要有用户ID、组ID、当前工作目录、根目录、打开的文件、创建文件时使用的屏蔽字
、信号屏蔽字、上下文环境、共享的存储段、资源限制等。
同时子进程与父进程还有一些不相同的属性:
- 子进程有它自己唯一的进程ID
- fork的返回值不同,父进程返回子进程的ID,子进程返回0
- 不同的父进程ID,子进程的父进程ID为创建它的父进程
- 子进程共享父进程打开的文件描述符,但父进程对文件描述符的改变不会影响子进程中的文件描述符
- 子进程不继承父进程设置的文件锁
- 子进程不继承父进程设置的警告
- 子进程的未决信号集被清空
信号的”未决“是一种状态,指的是从信号的产生到信号被处理前的这一段时间
9.孤儿进程
如果一个子进程的父进程先于子进程结束,子进程就成为一个孤儿进程,它由init(是内核启动的第一个用户级进程)进程收养,成为init进程的子进程
孤儿进程的测试代码如下:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main(void)
{
pid_t pid;
pid = fork();
switch(pid)
{
case 0:
{
while(1)
{
printf("child process is run, pid is %d, parent id is %d.\n", getpid(), getppid());
sleep(3);
}
break;
}
case -1:
{
perror("process creat failed:");
exit(-1);
break;
}
default:
{
printf("parent process is run!");
exit(0);
}
}
return 0;
}
由执行结果可以看出刚开始子进程的父进程pid为6994,当父进程执行结束退出后,子进程成为孤儿进程。
10.vfork函数
vfork也可以用来创建一个新进程,同样是调用一次,返回两次,与fork函数不同的是创建的子进程和父进程共享父进程的地址空间,子进程对该地址空间的修改对父进程同样有效。使用vfork创建的进程保证子进程先运行,当它调用exec或exit之后,父进程才有可能被调度运行。如果在调用exec或exit之前子进程要依赖父进程的某个行为,就会导致死锁。 当fork一个进程之后立即调用了exec执行另外一个应用程序,那么fork过程子进程对父进程的地址空间的复制将是一个多余的过程。vfork不会拷贝父进程的地址空间。11.创建守护进程
编写创建守护进程有如下要点:守护进程(daemon):指在后台运行的、没有控制终端与之相连的进程。
- 让进程在后台执行。调用fork产生一个子进程,然后使得父进程退出。
调用setsid创建一个新对话,控制终端,登陆会话和进程组通常是从父进程继承下来的,
当进程是会话组长时,调用setsid会失败(第一点已保证不是会话组长),setsid调用成功后,进程成为新的会话组长和进程组长,并与原来的登陆会话和进程组脱离,由于会话过后才能够对控制终端的独占性,进程同时与控制终端脱离
禁止进程重新打开控制终端。此时进程已经成为一个无终端的会话组长,但是它可以重新申请打开一个终端。为了避免可以通过使进程不再是会话组长来实现:再一次通过fork创建新的子进程,使调用fork的进程退出。
- 关闭不再需要的文件描述符。创建的子进程从父进程继承打开的文件描述符,如不关闭,回浪费系统资源,造成进程所在文件系统无法卸下以及引起无法预料的错误。先得到最高的文件描述符值,然后用一个循环程序,关闭0到最高文件描述符值的所有文件描述符。
- 将当前目录更改为根目录,当守护进程当前工作目录在一个装配文件系统中时,系统不能被拆卸。
- 将文字创建时使用的屏蔽字设置为0,进程会继承父进程使用的文件创建屏蔽字,这样在子进程中文件创建屏蔽字可能回拒绝某些许可权,为了防止,一般使用umask(0)将屏蔽字清零。
- 处理SIGCHLD信号。非必须,如果父进程不等待子进程结束,子进程将成为僵尸进程,占用系统资源,但当父进程等待子进程结束会增加父进程的负担,影响服务器进程的并发性。linux下可以将SIGCHLD信号的操作设为SIG_IGN,这样就不会产生僵尸进程。