欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

进程学习总结

程序员文章站 2024-03-12 15:07:26
...

进程的概念

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

依照我简单地理解:进程就是正在运行中的应用程序,它是一个动态的概念,他可以申请和拥有系统资源 。我们用c语言写的程序执行起来会产生进程。它具有动态性,并发行,独立性,异步行,交互性的特点。

特点解释

动态性不用解释,上面根据概念可以理解,并发性可以理解为一台计算机可以同时运行多个程序,比如你用一台计算机在登qq的同时可以打开视频播放器看电影,看起来这两件事是在一台计算机上是同时发生的,其实并不是,一个cpu不可能同时处理多个进程资源请求,只不过是cpu运行速度我们是无法用肉眼判别的,他每次只处理一个资源请求,当有多个进程请求资源,会出现多个进程抢cpu的情况,cpu会根据【调度算法】决定给那个程序分配资源,具体的调度算法实现,可以度娘,比较经典的很多,所以说进程的并发不是实际的并发,而是我们宏观上理解的并发,cpu终究是不能同一时刻处理多个进程的。独立性,可以理解为进程实体是一个可以独立运行的单位,同时也是系统中独立获得资源和调度的基本单位。异步性,由于计算机运行时多个进程之间相互制约(可以理解为抢cpu),导致进程出现“执行–暂停–执行….”,这种“走走停停”的以不可预知的速度运行的局面,而异步性就是这种不可预知的速度,何时开始何时暂停何时结束的性质。最后一个就是进程之间的交互性,也可以说是进程之间的通信,进程之间的通信有多种方式可以度娘一下,有详解,这一次进程学习并没有研究这方面的内容。

进程的标识

书上说是每个进程是通过唯一的id(非负数)标识的,也就相当于人的身份证号,这里要列出几种比较常用的获取进程id的函数

pid_t getpid()//获取进程id
pid_t getppid()//获取父进程id
pid_t getuid()//获取进程的实际用户id
pid_t geteuid()//获取进程的有效用户id
pid_t getgid() //获得进程的实际组id
pid_t getegid() //获得进程的有效组id
  • 按照书上解释一下这些有效和实际的意义吧!

实际用户ID:标识该进程的用户ID
实际组ID:实际用户所属的组ID
有效用户:标识以什么身份来运行进程,例如:某个普通用户A,运行了一个程序,而这个程序是以root身份来运行的,这程序运行时就具有root权限,此时实际用户ID是A用户的ID,而有效用户ID是root用户的ID
有效组ID:有效ID是有效用户所属的组的组ID

进程的状态

  • 就绪态。指进程已经获得所有所需的其他资源,正在申请处理处理器资源,准备开始执行。这种情况下,称进程处于就绪态。

  • 阻塞态。指进程因为需要等待所需资源而放弃处理器,或者进程本不拥有处理器,且其他资源也没有满足,从而即使得到处理器也不能开始运行。这种情况下,进程处于阻塞态。阻塞状态也称休眠状态或者等待状态。

  • 运行态。进程得到了处理器,并不需要等待其他任何资源,正在执行的状态,称之为运行态。只有在运行态时,进程才可以使用所申请到的资源。

  • 僵死状态

进程已终止,但进程描述符依然存在,知道父进程调用wait后函数释放。

进程控制及内存映像

在linux系统中,进程的控制主要系统调用如下:

  • fork:同于创建一个新进程。

函数原型:

#include<sys/types.h>
#include<unistd.h>
pid_t fork();

这个函数返回值有两个,一个是创建的子进程的ID,另一个是0或-1,我对其理解是:父进程在调用fork函数后,若创建成功,一个运行的程序会变成两个,即父进程和子进程,父进程返回创建的子进程的ID号,子进程中fork函数会再次返回一个0,所以fork()函数要想有两个返回值,子进程创建成功是前提。

所以一般创建子进程判断是否成功方法如下(省略主函数和头文件):

pid_t pid;//
if((pid=fork())<0){
    printf("创建进程失败!\n");
    return ;
}
else if(pid == 0){
    printf("子进程正在跑!\n");
}
else{
    printf("父进程正在跑!\n");
}

一般用fork创建子进程,不能确定创建成功之后是子进程先跑还是父进程先跑,cpu对两个进程的对待是平等的,只能看他们谁能抢到cpu,那谁就先执行。就有了基于fork()函数上的vfork()函数,用vfork创建子进程后,一定是子进程先跑,当他调用exec或exit之后,父进程才有可能被调度运行,如果在调用这两个函数之前,子进程要依赖于父进程的某个行为,就可能导致死锁(这个概念自己了解吧)。
这里就不多讲fork和vfork的区别了,可以点击那里,是推荐的好的博客,去了解吧!

  • exec:执行新进程。

这个函数族有太多的‘’兄弟姐妹‘’,还是有必要了解一下,我也没记住,再学习一遍吧:

#include<unistd.h>
int execve(const char * path ,char * const argv[],char *const envp[]);
//参数path是路径名,参数argv和参数envp和main函数中的参数对应
int execv(const char * path , char * const envp[]....);
//函数通过路径名调用可执行文件作为新进程映像
int execl(const char * path ,const char * arg ....);
//函数与execv函数用法类似,传参时每个命令行参数都声明为一个单独的参数
//使用“...”说明参数不止一个,参数以空指针作为结束,。
int execle(const char * path ,const char * arg ....);
//用法和楼上用法类似,只是要显示只是环境变量,位于空指针之后
int execlp(const char * file , const char *arg...);
//函数类似于execl,区别和excvo和execv一样
int execvp(const char * file ,char* const argv[] );
//函数用法与execv类似,不同的是参数filename。该参数如果包含“/”就相当于路径名,
//如果不包含,就会在环境变量中寻找
  • exit :终止进程。exit(0)进程正常终止,其他都为异常终止。
    • wait:(在后面介绍一些进程时会用到)将父进程挂起,等待子进程终止。
    • nice:改变进程优先级
#include<unistd.h>
int nice(int increment){//改变调用进程的优先级
    int oldpri = getpriority(PRIO_PROCESS,getpid());
    return setpriority(PRIO_PROCESS,oldpro+increment);
}
#include<sys/resource.h>
int getpriority(int which,inr who);//获取进程优先级
int setpriority(int which ,int who);//设置指定进程的优先级

nice系统调用是是上面两个函数的组合
用例:

int old_pri = getpriority(PRIO_PROCESS,0);//返回一组进程的优先级
int newpri =nice(2);/
exit(0);

进程的内存映像

程序和进程的最关键区别就在于一动一静,二者却是密切相关的,程序转化为进程主要经过以下步骤:

  • 内核将程序读入内存,为程序分配空间。
  • 内核为进程分配标识PID和所需资源。
  • 内核为进程保存PID和相应状态信息,把进程放在运行队列中等待系统调度执行。
进程内存映像主要包括

进程程序块,即被执行的程序,规定了进程一次运行应完成的功能。通常它是纯代码,作为一种系统资源可被多个进程共享。
进程数据块,即程序运行时加工处理对象,包括全局变量、局部变量和常量等的存放区以及开辟的工作区,常常为一个进程专用。
系统/用户堆栈,每一个进程都将捆绑一个系统/用户堆栈。用来解决过程调用或系统调用时的信息存储和参数传递。
进程控制块,每一个进程都将捆绑一个进程控制块,用来存储进程的标志信息、现场信息和控制信息。进程创建时,建立进程控制块;进程撤销时,回收进程控制块,它与进程一一对应。

这个是度娘下来的,以后方便看~~~~

可见每个进程有四个要素组成:控制块、程序块、数据块和堆栈。

几种比较典型进程

孤儿进程

如果一个子进程的的父进程先于子进程结束,子进程就会成为孤儿进程,他将由系统进程收养。成为系统进程的子进程。
创建:

pid_t pid;
pid =fork();
if(pid ==-1){
    printf("创建进程失败!\n");
    exit(-1);
}
else if(pid == 0){
    printf("儿子进程正在跑!\n");
    sleep(5);

} 
else{
    printf("父进程正在跑!\n");
    exit(0);
}
return  0;

这个不难创建,就让子进程多跑一会儿,然后只要执行到父进程,就正常结束他。然后正在跑的子进程就成为了系统进程的子进程,通常可以用它创建守护进程。

僵尸进程

在创建子进程后,子进程先与父进程退出时,父进程没有调用wait或waitpid函数,子进程就会成为僵尸进程。
wait和waitpid函数如下:

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *statioc);//参数是子进程的退出码
pid_t waitpid(pid_t pid ,int statioc,int options);//options允许用户改变waitpid的行为,一般情况下为0,如果想让父进程周期性检查某个特定的子进程是否已经退出,可以这样做:
waipid(child_pid ,(int*)0,WNOHANG);//如果子进程已经结束,返回子进程ID调用
//失败返回-1.未退出返回0。

举个例子:

pid_t pid ;
int status;
pid =fork();
if(pid<0){
    .....//你懂得
    }
if(pid == 0){
    pid_t pid2 ;
    int status2;
    pid2 = fork();//创建孙子进程
    if(pid2 <0){
        ....
    }
    else if(pid2 ==0) {
        .....
        exit(0);
    }
    if(wait(pid2,&status,0)<0){等待孙子进程结束

        //提示等待pid2号进程失败
    }
}
    exit(0);//退出子进程
    if(wait(pid,&status)<0){
        //报错
    }

(注释:上面程序没套main函数)
所以创建进程是当子进程先结束时,记得调用wait函数,不然的话,耗费资源不说,时候自己程序bug,都不知怎么找,写程序尽量避免出现僵尸进程,我是吃过亏的!!!

守护进程

守护进程是指后台运行的,没有控制终端与之相连的进程,独立与终端,周期性的执行某一任务,通常linux大多数的服务器就是用守护进程实现的,守护进程是很有用的进程!!
创建:
进程学习总结

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <time.h>
#include <syslog.h>

int init_daemon(void) 
{ 
    int pid; 
    int i; 

    //忽略终端I/O信号,STOP信号
    signal(SIGTTOU,SIG_IGN);
    signal(SIGTTIN,SIG_IGN);
    signal(SIGTSTP,SIG_IGN);
    signal(SIGHUP,SIG_IGN);

    pid = fork();
    if(pid > 0) {
        exit(0); //结束父进程,使得子进程成为后台进程
    }
    else if(pid < 0) { 
        return -1;
    }

    //建立一个新的进程组,在这个新的进程组中,子进程成为这个进程组的首进程,以使该进程脱离所有终端
    setsid();

    //再次新建一个子进程,退出父进程,保证该进程不是进程组长,同时让该进程无法再打开一个新的终端
    pid=fork();
    if( pid > 0) {
        exit(0);
    }
    else if( pid< 0) {
        return -1;
    }

    //关闭所有从父进程继承的不再需要的文件描述符
    for(i=0;i< NOFILE;close(i++));

    //改变工作目录,使得进程不与任何文件系统联系
    chdir("/");

    //将文件当时创建屏蔽字设置为0
    umask(0);

    //忽略SIGCHLD信号
    signal(SIGCHLD,SIG_IGN); 

    return 0;
}

int main() 
{ 
    time_t now;
    init_daemon();
    syslog(LOG_USER|LOG_INFO,"TestDaemonProcess! \n");
    while(1) { 
        sleep(8);
        time(&now); 
        syslog(LOG_USER|LOG_INFO,"SystemTime: \t%s\t\t\n",ctime(&now));
    } 
}

进程学习的更深层次的东西还很多,所以这儿可能就介绍了进程中的冰山一角而已,所以“路漫漫其修远兮,吾将上下而求索”,既然学习进程,这些是远远不够的,还是得坚持往深层次的研究。

相关标签: 进程学习小结