Linux 进程

2019-07-14 06:11发布

From:http://www.cnblogs.com/jacklu/p/5317406.html
From:http://www.cnblogs.com/mickole/p/3188321.html
Linux中fork()函数详解 :http://blog.chinaunix.net/uid-30025978-id-4696748.html

1. 进程控制块(PCB)

在Linux中task_struct结构体即是PCB。PCB是进程的唯一标识,PCB由链表实现(为了动态插入和删除)。 进程创建时,为该进程生成一个PCB;进程终止时,回收PCB。 PCB包含信息:1、进程状态(state);2、进程标识信息(uid、gid);3、定时器(time);4、用户可见寄存器、控制状态寄存器、栈指针等(tss)   每个进程都有一个非负唯一进程ID(PID)。虽然是唯一的,但是PID可以重用,当一个进程终止后,其他进程就可以使用它的PID了。 PID为0的进程为调度进程,该进程是内核的一部分,也称为系统进程;PID为1的进程为init进程,它是一个普通的用户进程,但是以超级用户特权运行;PID为2的进程是页守护进程,负责支持虚拟存储系统的分页操作。 除了PID,每个进程还有一些其他的标识符:



五种进程状态转换如下图所示:

每个进程的task_struct和系统空间堆栈存放位置如下:两个连续的物理页【《Linux内核源代码情景分析》271页】

系统堆栈空间不能动态扩展,在设计内核、驱动程序时要避免函数嵌套太深,同时不宜使用太大太多的局部变量,因为局部变量都是存在堆栈中的。

2. 进程的创建

新进程的创建,首先在内存中为新进程创建一个task_struct结构,然后将父进程的task_struct内容复制其中,再修改部分数据。分配新的内核堆栈、新的PID、再将task_struct 这个node添加到链表中。所谓创建,实际上是“复制”。   子进程刚开始,内核并没有为它分配物理内存,而是以只读的方式共享父进程内存,只有当子进程写时,才复制。即“copy-on-write”。 fork都是由do_fork实现的,do_fork的简化流程如下图:
在Linux系统中,所有的进程都是PID为1的init进程的后代。内核在系统启动的最后阶段启动init进程。该进程读取系统的初始化脚本(initscript)并执行其他的相关程序,最终完成系统启动的整个进程。

Linux提供两个函数去处理进程的创建和执行:fork()和exec()。首先,fork()通过拷贝当前进程创建一个子进程。子进程与父进程的区别仅仅在于PID(每个进程唯一),PPID(父进程的PID)和某些资源和统计量(例如挂起的信号)。exec()函数负责读取可执行文件并将其载入地址空间开始运行。

fork()使用写时拷贝(copy-on-write)页实现。内核在fork进程时不复制整个进程地址空间,让父进程和子进程共享同一个拷贝,当需要写入时,数据才会被复制,使各进程拥有自己的拷贝。在页根本不会被写入的情况下(fork()后立即exec()),fork的实际开销只有复制父进程的页表以及给子进程创建唯一的task_struct。

创建进程的fork()函数实际上最终是调用clone()函数。创建线程和进程的步骤一样,只是最终传给clone()函数的参数不同。比如,通过一个普通的fork来创建进程,相当于:clone(SIGCHLD, 0);创建一个和父进程共享地址空间,文件系统资源,文件描述符和信号处理程序的进程,即一个线程:clone(CLONE_VM | CLONE_FS | CLONE_FILES |CLONE_SIGHAND, 0)。

在内核中创建的内核线程与普通的进程之间还有个主要区别在于:内核线程没有独立的地址空间,它们只能在内核空间运行。

fork和vfork的区别

fork()与vfock()都是创建一个进程,那他们有什么区别呢?总结有以下三点区别:
  1. fork ():子进程拷贝父进程的数据段,代码段
    vfork ( ):子进程与父进程共享数据段
  2. fork ()父子进程的执行次序不确定
    vfork 保证子进程先运行,在调用exec 或exit 之前与父进程数据是共享的,在它调用exec或exit 之后父进程才可能被调度运行。
  3. vfork ()保证子进程先运行,在她调用exec 或exit 之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。


fork函数


fork函数时调用一次,返回两次。在父进程和子进程中各调用一次。子进程中返回值为0,父进程中返回值为子进程的PID。程序员可以根据返回值的不同让父进程和子进程执行不同的代码。 一个形象的过程:

运行这样一段演示程序:
#include #include #include int main() { pid_t pid; char *message; int n = 0; pid = fork(); while(1) { if(pid < 0) { perror("fork failed "); exit(1); } else if(pid == 0) { n--; printf("child's n is:%d ",n); } else { n++; printf("parent's n is:%d ",n); } sleep(1); } exit(0); } 运行结果:
可以发现子进程和父进程之间并没有对各自的变量产生影响。 一般来说,fork之后父、子进程执行顺序是不确定的,这取决于内核调度算法。进程之间实现同步需要进行进程通信。
通过for循环创建多个fork #include #include #include int main(int argc, char *argv[]) { int n = 5, i; //默认创建5个子进程 if(argc == 2) { n = atoi(argv[1]); } for(i = 0; i < n; i++) //出口1,父进程专用出口 if(fork() == 0) break; //出口2,子进程出口,i不自增 if(n == i) { sleep(n); printf("I am parent, pid = %d ", getpid()); } else { sleep(i); printf("I'm %dth child, pid = %d ", i+1, getpid()); wait(NULL); } return 0; } 代码(多进程): #include #include #include #include #include void sys_err(const char *str) { perror(str); exit(-1); } int main(int argc, char *argv[]) { pid_t pid; int i, n; if (argc < 2) { printf("./a.out numchild "); exit(-1); } n = atoi(argv[1]); for (i = 0; i < n; i++) { if ((pid = fork()) < 0) sys_err("fork"); else if (pid == 0) break; } /* 每个子进程中i的值都是不同的,父进程没有保存子进程的id */ if (pid == 0) { /* in child */ int m = 0; //while (m < i) { while (1) { printf("i = %d pid=%d ", i, getpid()); sleep(1); m++; } } if (pid > 0) { /* in parent */ pid_t cpid; while ((cpid = wait(NULL)) > 0) printf("child %d is over ", cpid); } return 0; } 多进程拷贝(实现类似 cp 命令): #include #include #include #include #include #include #include #include #define N 5 struct child_task { char src[256]; char dest[256]; int addr; int size; int num; }; void sys_err(const char *str) { perror(str); exit(-1); } int main(int argc, char *argv[]) { int nfork, i, task_addr, task_size, total_size, fddest, tmpfd; pid_t pid; struct stat sbuf; int *mem; if (argc < 3) { printf("./mycp src dest "); exit(-1); } if (argc == 3) nfork = N; else nfork = atoi(argv[3]); if (stat(argv[1], &sbuf) < 0) sys_err("stat"); total_size = sbuf.st_size; fddest = open(argv[2], O_CREAT|O_RDWR, 0644); if (fddest < 0) sys_err("open"); if (lseek(fddest, total_size-1, SEEK_SET) < 0) sys_err("lseek"); write(fddest, "