linux 进程常用概念

来源:互联网 发布:安卓2.1软件 编辑:程序博客网 时间:2024/04/29 03:01
当运行任何一个UNIX命令时,shell至少会建立一个进程来运行这个命令,所以可以把任何在UNIX系统中运行的程序叫做进程;但是进程并不是程序,进程是动态的,而程序是静态的,并且多个进程可以并发的调用同一个程序。
  
   系统中每一个进程都包含一个task_struct数据结构,所有指向这些数据结构的指针组成一个进程向量数组,系统缺省的进程向量数据大小是512,表示系统中可同时容纳512个进程。进程的task_struct数据结构包括了进程的状态、调度信息、进程标识符等信息。
  
   由于UNIX系统是一个多进程的操作系统,所以每一个进程都是独立的,都有自己的权限及任务,所以当某一进程失败时并不会导致别的进程失败。系统通过进程标识符来区分不同的进程,进程标识符是一个非负正数,他在任何时刻都是唯一的,当某个进程结束时,他的进程标识符可以分配给另外一个新进程。系统将标识符0分配给调度进程,标识符1分配给初始化进程。
  
   进程在运行期间,会用到很多资源,包括最宝贵的CPU资源,当某一个进程占用CPU资源时,别的进程必须等待正在运行的进程空闲CPU后才能运行,由于存在很多进程在等待,所以内核通过调度算法来决定将CPU分配给哪个进程。
  
   系统在刚刚启动时,运行于内核方式,这时候只有一个初始化进程在运行,他首先做系统的初始化,然后执行初始化程序(一般是/sbin/init)。初始化进程是系统的第一个进程,以后所有的进程都是初始化进程的子进程。
  
  2、进程的创建
  
   进程由fock函数创建,在unistd.h库中定义如下:
  
    #include 
    pid_t fock(void);
  
   fock函数调用一次却返回两次;在父进程中返回子进程的ID,在进程中返回0,这是因为父进程可能存在很多过子进程,所以必须通过这个返回的子进程ID来跟踪子进程,而子进程只有一个父进程,他的ID可以通过getppid取得。先面程序创建一个子进程:
  
   #include 
    #include 
    main() {
     pid_t pid;
     pid = fork();
     if(!pid)
      printf("this is child ");
     else if (pid>0)
      printf("this is parent,child has pid %d ",pid);
     else
      printf("fork fail ");
    }
  
   调用fock创建子进程后,父进程中所有打开的描述字在子进程中是共享的,这个特性在网络服务器中广泛使用,例如父进程通过socket函数返回一个套接字,然后调用fock函数创建字进程,这个子进程就可以直接对这个已经存在的套接字进行操作。fock的另一个典型应用是创建一个子进程调用exec函数来代替自己去执行新的程序。
  
  3、进程的执行
  
   UNIX系统执行以文件形式存储在磁盘上的可执行程序的唯一方法是用一个现有的进程去调用六个exec函数之一,六个exec函数定义如下:
  
   #include 
    int execl(const char *pathname,const char *arg0,.../* (char *)0*/);
    int execv(const char *pathname,char *const argv[]);
    int execle(const char *pathname,const char *arg0,... / (char *)0,
   char *const envp[]*/);
    int execve(const char * pathname,char *const argv[],char *const envp[]);
    int execlp(const char *filename,const char *arg0,.../* (char *)0*/);
    int execvp(const char *filename,char *const argv[]);
  
   在以上六个exec函数中,第一个参数如果为pathname,则说明被执行程序是由路径名指定,如果为filename,则说明是由文件名指定;第二个参数如果为数组,说明被执行程序的参数是由一个数组来索引(数组必须含有一个空指针来表示结束),否则需要将参数一一列出;execle()及execve()的envp指针数组表示这两个函数显示指定一个环境表(这个数组必须以一个空指针结束),而另外四个函数则用外部变量environ的当前值来创建一个环境表传递给新程序。
  
   exec函数把新程序装入调用进程的内存空间,来改变调用进程的执行代码。当exec函数执行成功,调用进程将被覆盖,相当于产生一个新进程,但是新进程的进程表识符号和调用进程的进程表识符相同,所以exec并没象fock函数一样创建一个新进程,而是用新进程取代了调用进程。下面是一个fork()结合exec()的程序:
  
   #include 
    #include 
    main() {
     int pid;
     pid = fork();
     switch(pid) {
      case -1:
          perror("fork failed");
          exit(1);
      case 0:
          execl("/bin/ls","ls","-l",NULL);
          perror("execl failed");
          exit(1);
     }
    }
  
  
  二、守护进程
  
  1、什么是守护进程
  
   守护进程是脱离于终端并且在后台运行的进程。守护进程脱离于终端是为了避免进程在执行过程中的信息不在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。
  
  2、守护进程的启动
  
   守护进程一般可以通过以下方式启动:
  
  A:在系统启动时由启动脚本启动,这些启动脚本通常放在/etc/rc.d目录下 B:利用inetd超级服务器启动,大部分网络服务都是这样启动的,如ftp、telnet等 C:另外,由cron定时启动以及在终端用nohup启动的进程也是守护进程 
  3、守护进程的错误输出
  
   由于守护进程是脱离于任何终端的,所以他的任何信息都无法直接输出到标准输出设备和标准错误输出设备中,在Linux系统中提供了syslog()系统调用来解决这个问题。syslog()在shslog.h定义如下:
  
   #include 
   void syslog(int priority,char *format,...);
  
   其中参数priority指明了进程要写入信息的等级和用途。等级见表1,用途见表2:
   
  
  第二个参数是一个格式串,指定了记录输出的格式,在这个串的最后需要指定一个%m,对应errno错误码。
  
  4、守护进程的建立
  
   首先fock一个子进程并关闭父进程,如果此进程是作为一个shell命令在控制台执行的,当父进程被终止时,shell就认为该命令已经结束,这时子进程自动转为后台运行,由于子进程在父进程那里继承了组标识符同时又拥有自己的进程标识符,就保证了子进程不是一个进程组的首进程;接着调用setsid()创建一个新进程组,调用进程成为该进程组的首进程,这就使该进程脱离于原终端;接下来重新调用fock(),使进程不再是进程组的首进程,从而忽略SIGHUP信号,以避免在某些情况下进程以外打开终端而重新与终端发生联系;最后改变工作目录,关闭全部已经打开的文件句柄,并打开log系统,到此守护进程成功建立。