首页 > 代码库 > LINUX进程

LINUX进程

什么是进程

(1)动态过程而不是静态实物
(2)进程就是程序的一次运行过程,一个静态的可执行程序a.out的一次运行过程(./a.out去运行到结束)就是一个进程。

进程ID

相关API getpid(自己)、getppid(父进程)、getuid、geteuid、getgid、getegid

fork创建子进程

(1)进程的分裂生长模式。如果操作系统需要一个新进程来运行一个程序,那么操作系统会用一个现有的进程来复制生成一个新进程。老进程叫父进程,复制生成的新进程叫子进程。
(2)fork函数调用一次会返回2次,返回值等于0的就是子进程,而返回值大于0的就是父进程。
(3)典型的使用fork的方法:使用fork后然后用if判断返回值,并且返回值大于0时就是父进程,等于0时就是子进程。
(4)fork的返回值在子进程中等于0,在父进程中等于本次fork创建的子进程的进程ID。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
    pid_t p1 = -1,p2 = -1;
    int i=110;
    p1= fork();
    if(0 == p1)
    {
        ///子进程
        printf("子进程%d\n",i);    
        printf("hello world, pid = %d \n",getpid());    
        return 0;
    }
    if(0 < p1)
    {
        ///父进程
        i=2;
        printf("父进程%d\n",i);    
        printf("hello world, pid = %d \n",getpid());        
        return 0;
    }
    if(0 > p1)
    {
        ///错误
    }    
    return 0;
}

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

进程的消亡

(1)正常终止:return、exit、_exit
(2)非正常终止:自己或他人发信号终止进程

atexit注册进程终止处理函数:return和exit效果一样,都是会执行进程终止处理函数,但是用_exit终止进程时并不执行atexit注册的进程终止处理函数。

僵尸进程

(1)子进程先于父进程结束。子进程结束后父进程此时并不一定立即就能帮子进程“收尸”,在这一段(子进程已经结束且父进程尚未帮其收尸)子进程就被成为僵尸进程。
(2)子进程除task_struct和栈外其余内存空间皆已清理
(3)父进程可以使用wait或waitpid以显式回收子进程的剩余待回收内存资源并且获取子进程退出状态。
(4)父进程也可以不使用wait或者waitpid回收子进程,此时父进程结束时一样会回收子进程的剩余待回收内存资源。(这样设计是为了防止父进程忘记显式调用wait/waitpid来回收子进程从而造成内存泄漏)

孤儿进程

(1)父进程先于子进程结束,子进程成为一个孤儿进程。
(2)linux系统规定:所有的孤儿进程都自动成为一个特殊进程(进程1,也就是init进程)的子进程。

父进程wait回收子进程

(1)子进程结束时,系统向其父进程发送SIGCHILD信号
(2)父进程调用wait函数后阻塞
(3)父进程被SIGCHILD信号唤醒然后去回收僵尸子进程
(4)父子进程之间是异步的,SIGCHILD信号机制就是为了解决父子进程之间的异步通信问题,让父进程可以及时的去回收僵尸子进程。
(5)若父进程没有任何子进程则wait返回错误

waitpid和wait差别

(1)基本功能一样,都是用来回收子进程
(2)waitpid可以回收指定PID的子进程
(3)waitpid可以阻塞式或非阻塞式两种工作模式

exec族函数

(1)execl和execv 这两个函数是最基本的exec,都可以用来执行一个程序,区别是传参的格式不同。execl是把参数列表(本质上是多个字符串,必须以NULL结尾)依次排列而成(l其实就是list的缩写),execv是把参数列表事先放入一个字符串数组中,再把这个字符串数组传给execv函数。
(2)execlp和execvp 这两个函数在上面2个基础上加了p,较上面2个来说,区别是:上面2个执行程序时必须指定可执行程序的全路径(如果exec没有找到path这个文件则直接报错),而加了p的传递的可以是file(也可以是path,只不过兼容了file。加了p的这两个函数会首先去找file,如果找到则执行执行,如果没找到则会去环境变量PATH所指定的目录下去找,如果找到则执行如果没找到则报错)
(3)execle和execvpe 这两个函数较基本exec来说加了e,函数的参数列表中也多了一个字符串数组envp形参,e就是environment环境变量的意思,和基本版本的exec的区别就是:执行可执行程序时会多传一个环境变量的字符串数组给待执行的程序。

(4)execlp和execvp 加p和不加p的区别是:不加p时需要全部路径+文件名,如果找不到就报错了。加了p之后会多帮我们到PATH所指定的路径下去找一下。

(5)execle和execvpe    

1、main函数的原型其实不止是int main(int argc, char **argv),而可以是int main(int argc, char **argv, char **env) 第三个参数是一个字符串数组,内容是环境变量。
2、如果用户在执行这个程序时没有传递第三个参数,则程序会自动从父进程继承一份环境变量(默认的,最早来源于OS中的环境变量);如果我们exec的时候使用execlp或者execvpe去给传一个envp数组,则程序中的实际环境变量是我们传递的这一份(取代了默认的从父进程继承来的那一份)

 

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
    pid_t p1 = -1;
    int status = -1;
    int i=110;
    p1= fork();
    if(0 == p1)
    {
        char * const arg[]={"ls","-a","-l",NULL};
        char * const envp[]={"A=XX","B=OO",NULL};
        //execl("/bin/ls","ls","-l","-a",NULL);
        execle("main_env","main_env",NULL,envp);//执行第一个后后面的无法执行
        execlp("ls","ls","-l","-a",NULL);
        //execv("/bin/ls",arg);
        execl("hello","hello",NULL);
        
        return 123;
    }
    if(0 < p1)
    {
        printf("父进程%d\n",i);    
        return 0;
    }
    if(0 > p1)
    {
        ///错误
    }    
    return 0;
}

 

进程状态和system函数

(1)就绪态。这个进程当前所有运行条件就绪,只要得到了CPU时间就能直接运行。
(2)运行态。就绪态时得到了CPU就进入运行态开始运行。
(3)僵尸态。进程已经结束但是父进程还没来得及回收
(4)等待态(浅度睡眠&深度睡眠),进程在等待某种条件,条件成熟后可进入就绪态。等待态下就算你给他CPU调度进程也无法执行。浅度睡眠等待时进程可以被(信号)唤醒,而深度睡眠等待时不能被唤醒只能等待的条件到了才能结束睡眠状态。
(5)暂停态。暂停并不是进程的终止,只是被被人(信号)暂停了,还可以回复

技术分享

 

system函数简介

(1)system函数 = fork+exec
(1)原子操作。原子操作意思就是整个操作一旦开始就会不被打断的执行完。原子操作的好处就是不会被人打断(不会引来竞争状态),坏处是自己单独连续占用CPU时间太长影响系统整体实时性,因此应该尽量避免不必要的原子操作,就算不得不原子操作也应该尽量原子操作的时间缩短。
(2)使用system调用ls

进程关系

(1)无关系
(2)父子进程关系
(3)进程组(group)由若干进程构成一个进程组
(4)会话(session)会话就是进程组的组

何谓守护进程

(1)daemon,表示守护进程,简称为d(进程名后面带d的基本就是守护进程)
(2)长期运行(一般是开机运行直到关机时关闭)
(3)与控制台脱离(普通进程都和运行该进程的控制台相绑定,表现为如果终端被强制关闭了则这个终端中运行的所有进程都被会关闭,背后的问题还在于会话)
(4)服务器(Server),服务器程序就是一个一直在运行的程序,可以给我们提供某种服务(譬如nfs服务器给我们提供nfs通信方式),当我们程序需要这种服务时我们可以调用服务器程序(和服务器程序通信以得到服务器程序的帮助)来进程这种服务操作。服务器程序一般都实现为守护进程。

编写简单守护进程

(1)子进程等待父进程退出
(2)子进程使用setsid创建新的会话期,脱离控制台
(3)调用chdir将当前工作目录设置为/
(4)umask设置为0以取消任何文件权限屏蔽
(5)关闭所有文件描述符
(6)将0、1、2定位到/dev/null

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

void create_daemon(void);
int main()
{
    create_daemon();
    while(1)
    {
    printf("I am running\n");
    sleep(1);
    }
    
    return 0;
}
void create_daemon(void)
{
    pid_t p1 = -1;
    p1= fork();
    if(0 < p1)//父进程
    {
        exit(0);
    }
    if(0 > p1)
    {
        perror("fork");
        exit(-1);
        ///错误
    }
    //子进程
    p1=setsid();//设置新的会话器 脱离控制台
    if(p1<0)
    {
        perror("setsid");
        exit(-1);
    }
    chdir("/");//设置工作目录
    umask(0);//确保最大权限
    for(int i =0 ;i <sysconf(_SC_OPEN_MAX);i++)
    {
        close(i);//关闭所有文件描述符
    }
    open("/dev/null",O_RDWR);//将描述符 123 定位到/dev/null
    open("/dev/null",O_RDWR);
    open("/dev/null",O_RDWR);
}

使用syslog来记录调试信息

openlog、syslog、closelog

一般log信息都在操作系统的/var/log/messages这个文件中存储着,但是ubuntu中是在/var/log/syslog文件中的。

(1)操作系统中有一个守护进程syslogd(开机运行,关机时才结束),这个守护进程syslogd负责进行日志文件的写入和维护。
(2)syslogd是独立于我们任意一个进程而运行的。我们当前进程和syslogd进程本来是没有任何关系的,但是我们当前进程可以通过调用openlog打开一个和syslogd相连接的通道,然后通过syslog向syslogd发消息,然后由syslogd来将其写入到日志文件系统中。
(3)syslogd其实就是一个日志文件系统的服务器进程,提供日志服务。任何需要写日志的进程都可以通过openlog/syslog/closelog这三个函数来利用syslogd提供的日志服务。这就是操作系统的服务式的设计。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <syslog.h> 
int main()
{
    openlog("a.out",LOG_PID|LOG_CONS,LOG_USER);
    syslog(LOG_INFO,"this is my log in info songpengfei %d\n",1234);
    closelog();//ubuntu 中日志在 /var/log/syslog
    return 0;
}

 

LINUX进程