linux进程控制总结一

来源:互联网 发布:淘宝装修图片如何制作 编辑:程序博客网 时间:2024/06/05 04:29

在执行一个可执行文件的过程就是一个进程,首先我们要看一个进程很重要的结构图:
这里写图片描述
正文段:这是由CPU执行的机器指令部分,通常正文段是可共享的,所以即使是频繁执行的程序在存储器中也只有一个副本,另外正文段常是只读的,防止程序由于意外而修改其指令;
初始化数据段:通常也成为数据段,它包含了程序中需明确的赋值的变量,例如: int i=10;让变量以其初始值存放在初始化数据段中。
未初始化数据段:也称bss段,在程序执行前,内核将此段的数据初始化为0或者空指针。例如: int ar[10];使此变量存放在未出世化的数据段中。
栈:自动变量以及每次函数调用所需保存的信息都放在该段中。
堆:通常在堆中进行动态内存分配。

进程标识:

每个进程都有一个非负整数的唯一进程ID,虽然进程ID是唯一的,但可以复用,当一个进程结束后它的ID可以供其他进程用。 系统中有一些专用进程,ID为0的进程通常是调度进程,常常被称为交换进程,该进程是内核的一部分,它并不执行任何磁盘上的程序,因此被称为系统进程。进程ID为1通常是init进程,在自举过程结束时由内核调用,该进程的程序文件在UNIX的早期版本中是/etc/init,在较新的版本中是/sbin/init,此进程负责在自举内核后启动一个UNIX系统,它是一个普通的用户进程,与交换进程不同它不是内核中的系统进程,但它以超级用户特权运行,它还是孤儿进程的父进程。下面我们来看看进程的创建和使用的一些函数:

1、查看进程相关ID函数

#include<unistd.h>pid_t getpid(); //查看进程的ID号pid_t getppid(); //查看父进程的ID号pid_t getuid() ;//查看进程的实际用户ID号pid_t geteuid(); //查看进程的有效用户ID号get_t getgid(); //查看进程的实际组ID号get_t getegid(); //查看进程的有效组

实际用户实际组和有效用户有效组有什么区别呢?
实际用户实际组就是你现在所在地和该地所属区域;
有效用户和有效组就是你出生地和出生地所属区域。

2、创建新进程函数fork和vfork:

#include<unistd.h>pid_t fork(void);

fork函数可以创建一个新的进程,它的使用简单用一句话概括就是:fork执行成功一次有两个返回值,父进程返回子进程的ID号,子进程返回0;执行失败返回-1。下面我们来看看简单的fork()函数实现:

#include<iostream>#include<unistd.h>#include<stdio.h>#include<stdlib.h>#include<sys/wait.h>using namespace std;int main(){    pid_t pid = fork();    if(pid == 0)    {        cout<<"This is Child:> "<<getpid()<<endl;    }    else if(pid > 0)    {        cout<<"This is parent:>"<<getpid()<<"   The child is"<<pid<<endl;        int mutex;        wait(&mutex);//没调用一次只等一个子进程的结束,子进程多需多次调用,否则容易出现僵尸进程 #include<sys/wait.h>    }    else    {        perror("fork:> ");//使用man手册查看        //cout<<"fork fail."<<endl;    }    return 0;}

运行结果:
这里写图片描述
fork在创建一个子进程的时候,子进程会把父进程从fork函数开始以下几乎所有的资源都要复制,所以fork是一个开销很大的系统调用。
fork 失败的原因主要有两个:(1)系统中已经有了太多的进程(通常意味着某个方面出了问题),(2)该实际用户ID的进程总数超出了系统限制。
fork有以下两种用法:(1)一个父进程希望复制自己,使父进程子进程同时执行不同的代码。这在网络服务进程中是常见的——父进程等待客户端的服务要求。当这种要求到达时,父进程调用fork,使子进程处理次请求。父进程则继续等待下一个服务请求。(2)一个进程要执行不同的程序。
3、fork的兄弟vfork:
vfork函数的调用序列和返回值与fork相同,当两者的语义不同。vfork函数和fork函数都是创建一个子进程,但它并不是将父进程的地址空间完全复制给子进程,因为子进程会随时调用exec或者exit,于是也就不会引用该地址空间。不过在子进程调用exec或exit之前它在父进程的空间中运行。
fork和vfork之间的另外一个区别就是:vfork保证子进程先运行,在它调用exec或exit之后父进程才能被调度运行,当子进程调用这两个函数中的任意一个时,父进程会恢复运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步工作,则会造成死锁。)
下面我们来简单的看看vfork创建子进程过程和结果:

#include<iostream>#include<unistd.h>#include<stdio.h>using namespace std;int main(){    pid_t pid = vfork;    if(pid < 0)    {        perror("vfork:> ");        exit(1);//异常退出        //_exit(1);//二者区别在于exit在退出的时候会做一些处理,之后到内核;_exit和_Exit是直接退出到内核。    }    else if(pid == 0)    {        cout<<"This is Child:> "<< getpid()<<" ppid="<<getppid<<endl;        sleep(2);//验证父进程能够在子进程运行完退出        cout<<"Child wake up,pid = "<<getpid()<<"  ppid = "<<getppid()<<endl;        exit(0);//正常退出,在子进程执行完成执行退出    }    else    {        cout<<"this is parent:> "<<getpid()<<" Child:>"<<pid<<endl;    }    return 0;}

运行结果如下:
这里写图片描述

4、子进程的继承和不继承:

子进程会继承父进程的很多属性,主要包括ID、组ID、当前工作目录、根目录、打开的文件、创建文件时使用的屏蔽字、信号屏蔽字、上下文环境、共享的存储环段、资源限制等。子进程与父进程也有一些不同的属性,如:子进程有自己唯一的ID;fork的返回值不同;不同的父进程ID,子进程的父进程ID为创建它的进程ID;子进程共享父进程打开的文件描述符,但父进程对文件描述符的改变不会影响子进程中的文件描述;子进程不继承父进程设置的文件锁;子进程不继承父进程设置的警告;子进程的未决信号集被清空。

5、常见的进程:

正常终老进程:就是我们平时练习写的那些进程,父进程等待子进程结束后才结束的进程。
孤儿进程:父进程先于子进程结束,子进程称为孤儿进程交由进程ID号为1的进程托管,并收尸。
守护进程:是指在后台运行的、没有控制终端与之相连的进程。
僵尸进程:表现为一个进程结束了,但它的父进程没有等待(调用wait / waitpid)它, 那么该进程会变成一个僵尸进程。 一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁, 而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是 使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。
下一篇我们来看看这几种进程的实现和他们的优缺点,如何防范以及避免。

0 1
原创粉丝点击