进程的状态
进程的创建
在Linux中主要提供了fork、vfork、clone三个进程创建方法。
fork()调用执行一次返回两个值,对于父进程,fork函数返回子程序的进程号,而对于子程序,fork函数则返回零,这就是一个函数返回两次的本质。
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
int main()
{
int a = 5;
int b = 2;
pid_t pid;
pid = fork();
if(pid == 0) {
a = a-4;
printf("I'm a child process with PID [%d],the value of a: %d,the value of b:%d.\n",pid,a,b);
}else if(pid < 0) {
perror("fork");
}else {
printf("I'm a parent process, with PID [%d], the value of a: %d, the value of b:%d.\n", pid, a, b);
}
return 0;
}
#gcc –o fork fork.c
#./fork
//运行结果:
I’m a child process with PID[0],the value of a:1,the value of b:2.
I’m a parent process with PID[19824],the value of a:5,the value of b:2.
vfork系统调用不同于fork,用vfork创建的子进程与父进程共享地址空间,也就是说子进程完全运行在父进程的地址空间上,如果这时子进程修改了某个变量,这将影响到父进程。
用 vfork创建子进程后,父进程会被阻塞直到子进程调用exec(exec,将一个新的可执行文件载入到地址空间并执行之。)或exit。vfork的好处是在子进程被创建后往往仅仅是为了调用exec执行另一个程序,因为它就不会对父进程的地址空间有任何引用,所以对地址空间的复制是多余的 ,因此通过vfork共享内存可以减少不必要的开销。
clone
系统调用fork()和vfork()是无参数的,而clone()则带有参数。fork()是全部复制,vfork()是共享内存,而clone() 是则可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的 clone_flags来决定。另外,clone()返回的是子进程的pid。
进程间通信(IPC)
linux号称提供六大IPC机制。
-
管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
-
信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期 信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上, 该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,sigaction函数重新实现了signal函数);
-
报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
-
共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针其他通信机制运行效率较低设计的。往往与其它通信机制,如信号量结合使用, 来达到进程间的同步及互斥。
-
信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
-
套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix 系统上:Linux和System V的变种都支持套接字。
socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags Type State I-Node PID/Program name Path
unix 2 [ ] DGRAM 1390 - /run/systemd/notify
unix 2 [ ACC ] STREAM LISTENING 14703 - /var/run/rpcbind.sock
unix 2 [ ] DGRAM 1392 - /run/systemd/cgroups-agent
unix 2 [ ACC ] STREAM LISTENING 14707 - /var/run/dbus/system_bus_socket
unix 2 [ ACC ] STREAM LISTENING 1401 - /run/systemd/journal/stdout
unix 5 [ ] DGRAM 1404 - /run/systemd/journal/socket
unix 9 [ ] DGRAM 1406 - /dev/log
unix 2 [ ACC ] STREAM LISTENING 12678 - /run/systemd/private
unix 2 [ ACC ] STREAM LISTENING 12694 - /run/lvm/lvmetad.socket
Linux中进程的实现
task_struct是进程描述符。操作系统通过task_struct感知进程的存在。也就是我们所说的PCB。
该task_struct的结构如下
struct task_struct {
volatile long state; //说明了该进程是否可以执行,还是可中断等信息
unsigned long flags; //Flage 是进程号,在调用fork()时给出
intsigpending; //进程上是否有待处理的信号
mm_segment_taddr_limit; //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
//0-0xBFFFFFFF foruser-thead
//0-0xFFFFFFFF forkernel-thread
//调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
volatilelong need_resched;
int lock_depth; //锁深度
longnice; //进程的基本时间片
//进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR,分时进程:SCHED_OTHER
unsigned long policy;
struct mm_struct *mm; //进程内存管理信息
int processor;
//若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1这个值在运行队列被锁时更新
unsigned long cpus_runnable, cpus_allowed;
struct list_head run_list; //指向运行队列的指针
unsigned longsleep_time; //进程的睡眠时间
//用于将系统中所有的进程连成一个双向循环链表,其根是init_task
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm;
struct list_headlocal_pages; //指向本地页面
unsigned int allocation_order, nr_local_pages;
struct linux_binfmt *binfmt; //进程所运行的可执行文件的格式
int exit_code, exit_signal;
intpdeath_signal; //父进程终止是向子进程发送的信号
unsigned longpersonality;
//Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序
intdid_exec:1;
pid_tpid; //进程标识符,用来代表一个进程
pid_tpgrp; //进程组标识,表示进程所属的进程组
pid_t tty_old_pgrp; //进程控制终端所在的组标识
pid_tsession; //进程的会话标识
pid_t tgid;
intleader; //表示进程是否为会话主管
struct task_struct*p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
struct list_head thread_group; //线程链表
struct task_struct*pidhash_next; //用于将进程链入HASH表
struct task_struct**pidhash_pprev;
wait_queue_head_t wait_chldexit; //供wait4()使用
struct completion*vfork_done; //供vfork()使用
unsigned long rt_priority; //实时优先级,用它计算实时进程调度时的weight值
//it_real_value,it_real_incr用于REAL定时器,单位为jiffies,系统根据it_real_value
//设置定时器的第一个终止时间.在定时器到期时,向进程发送SIGALRM信号,同时根据
//it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。
//当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送
//信号SIGPROF,并根据it_prof_incr重置时间.
//it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种
//状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据
//it_virt_incr重置初值。
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_value;
struct timer_listreal_timer; //指向实时定时器的指针
struct tmstimes; //记录进程消耗的时间
unsigned longstart_time; //进程创建的时间
//记录进程在每个CPU上所消耗的用户态时间和核心态时间
longper_cpu_utime[NR_CPUS],per_cpu_stime[NR_CPUS];
//内存缺页和交换信息:
//min_flt, maj_flt累计进程的次缺页数(Copyon Write页和匿名页)和主缺页数(从映射文件或交换
//设备读入的页面数);nswap记录进程累计换出的页面数,即写到交换设备上的页面数。
//cmin_flt, cmaj_flt,cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。
//在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中
unsignedlong min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable:1; //表示进程的虚拟地址空间是否允许换出
//进程认证信息
//uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid
//euid,egid为有效uid,gid
//fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件
//系统的访问权限时使用他们。
//suid,sgid为备份uid,gid
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
int ngroups; //记录进程在多少个用户组中
gid_t groups[NGROUPS]; //记录进程所在的组
//进程的权能,分别是有效位集合,继承位集合,允许位集合
kernel_cap_tcap_effective, cap_inheritable, cap_permitted;
int keep_capabilities:1;
struct user_struct *user;
struct rlimit rlim[RLIM_NLIMITS]; //与进程相关的资源限制信息
unsigned shortused_math; //是否使用FPU
charcomm[16]; //进程正在运行的可执行文件名
//文件系统信息
int link_count, total_link_count;
//NULL if no tty进程所在的控制终端,如果不需要控制终端,则该指针为空
struct tty_struct*tty;
unsigned int locks;
//进程间通信信息
struct sem_undo*semundo; //进程在信号灯上的所有undo操作
struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作
//进程的CPU状态,切换时,要保存到停止进程的task_struct中
structthread_struct thread;
//文件系统信息
struct fs_struct *fs;
//打开文件信息
struct files_struct *files;
//信号处理函数
spinlock_t sigmask_lock;
struct signal_struct *sig; //信号处理函数
sigset_t blocked; //进程当前要阻塞的信号,每个信号对应一位
struct sigpendingpending; //进程上是否有待处理的信号
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
u32 parent_exec_id;
u32 self_exec_id;
spinlock_t alloc_lock;
void *journal_info;
};
一些特殊进程
- 孤儿进程
如果父进程先退出,子进程还没退出那么子进程将被 托孤给init进程,这是子进程的父进程就是init进程(1号进程) - 僵尸进程
进程终止后进入僵死状态(zombie),等待告知父进程自己终止,后才能完全消失.但是如果一个进程已经终止了,但是其父进程还没有获取其状态,那么这个进程就称之为僵尸进程.僵尸进程还会消耗一定的系统资源,并且还保留一些概要信息供父进程查询子进程的状态可以提供父进程想要的信息.一旦父进程得到想要的信息,僵尸进程就会结束.