• Linux 进程


     

    code my life

    Linux 进程

    2013-07-08 09:31        by        cococo点点,        801阅读,        5评论,        收藏,        编辑

     

      在用户空间,进程是由进程标识符(PID)表示的。从用户的角度来看,一个 PID 是一个数字值,可惟一标识一个进程。一个 PID 在进程的整个生命期间不会更改,但 PID 可以在进程销毁后被重新使用,所以对它们进行缓存并不见得总是理想的。

    进程表示

      在 Linux 内核内,进程是由相当大的一个称为 task_struct 的结构表示的。此结构包含所有表示此进程所必需的数据,此外,还包含了大量的其他数据用来统计(accounting)和维护与其他进程的关系(父和子)。

    struct task_struct {
    
        volatile long state;
        void *stack;
        unsigned int flags;
    
        int prio, static_prio;
    
        struct list_head tasks;
    
        struct mm_struct *mm, *active_mm;
    
        pid_t pid;
        pid_t tgid;
    
        struct task_struct *real_parent;
    
        char comm[TASK_COMM_LEN];
    
        struct thread_struct thread;
    
        struct files_struct *files;
    
        ...
    
    };
    View Code

    进程管理

      在很多情况下,进程都是动态创建并由一个动态分配的 task_struct 表示。一个例外是init 进程本身,它总是存在并由一个静态分配的 task_struct 表示。

      在 Linux 内虽然进程都是动态分配的,但还是需要考虑最大进程数。在内核内最大进程数是由一个称为max_threads 的符号表示的,它可以在 ./linux/kernel/fork.c 内找到。可以通过 /proc/sys/kernel/threads-max 的 proc 文件系统从用户空间更改此值。

      Linux进程分为两种基本类型,分别为内核进程用户进程。内核进程通过内核中kernel_thread()函数创建的,用户进程通过fork()和clone()创建。

    进程的创建&内存的复制

      创建一个子进程,就创建了一个新的子任务,并为它复制了父进程的内存。但是这两个进程使用的内存是相互独立的。在调用fork函数的时候,父进程当时的所有变量对子进程都是可见的,fork函数执行完成之后,父进程的变量对于子进程来说就隐藏了。

      在创建一个新进程时,父进程使用的内存并不是真正的全部复制给子进程。它们都指向同一处内存空间,但是把内存页面标记为copy-on-write。当任何一个进程试图向这些内存中写入内容时,就会产生一组新的内存页面由这个进程私有。这样,通过这种方法提高了创建新进程的效率,因为内存空间的复制推迟到了发生写操作的时候。

    进程相关API

      Linux下进程模型提供了很多API函数,下面的这些函数可以完成基本的工作。

    API函数 用途
    fork 创建子进程
    wait 将进程挂起,直到子进程退出
    waitpid      将进程挂起,直到指定子进程退出
    signal 注册新的信号
    pause  将进程挂起,直到捕捉到信号
    kill 向某个指定的进程发出信号
    raise 向当前进程发出信号
    exec 将当前进程映像用一个新的进程映像来替换
    exit 正常终止当前进程

    函数详解&具体应用

      日常编程中,我们常用到多进程的方式,可以让我们的程序同步执行多个任务。接下来,从函数原型依次介绍并用实例来分析。

    进程的创建fork

      API函数fork用于在一个已经存在的父进程中创建一个新的进程,子进程除了ID与父进程不同,其余都相同。

    pid_t fork( void );
    //pid_t是进程描述符
    //返回值:>0, 当前进程就是父进程;==0,当前进程就是子进程;<0,失败 //返回值<0时,有两种错误,都是内存不足问题,分别为EAGAIN/ENOMEM
     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <sys/types.h>
     4 
     5 int main()
     6 {
     7     pid_t pid;
     8     
     9     pid = fork();
    10     if(pid > 0)
    11         printf("Parent process pid: %d
    ",getpid());
    12     else if(pid == 0)
    13         printf("Child process pid: %d
    ",getpid());
    14     else
    15         printf("fork error!
    ");
    16     
    17     return 0;
    18 }
    View Code

    进程关闭exit

      API函数exit终止调用进程,传入exit的参数会返回给父进程。

      子进程调用exit函数还会向父进程发出SIGCHLD信号,释放当前进程的资源。

    //关闭进程
    void exit( int status ); //status用来保存状态信息

    信号signal

      signal_handler函数允许用户为进程注册信号句柄。在信号被注册后,就可以在需要的时候调用信号API函数。

      PS: 在做一个WIN32控制台下的程序时候需要动态控制,我就利用SIGINT信号,在程序运行的过程中使用CTRL+C,调用中断函数完成操作。

    复制代码
    //信号句柄
    void signal_handler( int signal_number );
    
    //注册信号
    sighandler_t signal( int signum, sighandler_t handler );
    //signum为信号类型
    //handler为与信号相关联的动作
    //sighandler_t的类型定义:typedef void (*sighandler_t)(int) *****指向函数的指针******
    复制代码

      进程的信号句柄分为三种类型,分别为忽略类型、默认指定类型和用户自定义句柄类型。

      例子:

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <sys/types.h>
     4 #include <signal.h>
     5 
     6 void catch_signal_ctrl_c(int sig_num)
     7 {
     8     printf("catch signal ctrl+n");
     9     fflush(stdout);
    10 }
    11 
    12 int main()
    13 {
    14     signal(SIGINT, catch_signal_ctrl_c);       //注册信号SIGINT软件中断,通过输入ctrl+c可以发出
    15     
    16     printf("run a pause");
    17     pause();                          //进程挂起,等待信号
    18     
    19     return 0;
    20 }
    View Code

    进程挂起pause

      函数pause会把进程挂起,直到接收到信号。在信号接收后,进程会从pause函数中退出,继续运行。

    //挂起
    int pause( void );

    向当前进程发送信号raise

      raise函数只可以向当前进程发出信号。

    int raise( int sig_num );

      例子:

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <sys/types.h>
     4 #include <signal.h>
     5 
     6 void catch_signal_ctrl_c(int sig_num)
     7 {
     8     printf("catch signal ctrl+n");
     9     fflush(stdout);
    10 }
    11 
    12 int main()
    13 {
    14     signal(SIGINT, catch_signal_ctrl_c);       //注册信号SIGINT软件中断,通过输入ctrl+c可以发出
    15     
    16     raise(SIGINT);
    17     
    18     return 0;
    19 }
    View Code

    向其他进程发送信号kill

      kill函数可以向一个进程或一系列进程发送信号。

    int kill( pid_t pid, int sig_num );

      参数pid的不同会有不同的效果:

    pid 说明
    >0   发送信号到pid指定进程
    0 发送信号到与本进程同组的所有进程
    -1 发送信号到所有进程(init进程除外)
    <0 发送信号到由pid绝对值指定的进程组中的所有进程

    进程挂起wait&waitpid

      wait函数与waitpid函数都是将进程挂起,直到某个进程退出或信号发生,避免僵尸进程的产生。

      子进程退出,父进程没有等待(调用wait / waitpid)它, 那么她将变成一个僵尸进程。 但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程, 因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程, 看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init 来接管它,成为它的父进程

      wait只根据任何一个子进程退出状态。

    pid_t wait( int *status );
    //返回退出子进程的pid值,如果为-1,发生错误
    //status用来保存子进程退出的状态信息

      通过评估函数可以知道子进程退出的状态信息。

    说明
    WIFEXITED 如果子进程正常退出,则不为0 
    WEXITSTATUS   返回子进程的exit状态
    WIFSIGNALED 如果子进程因为信号退出,则为ture
    WTERMSIG 返回引起子进程退出的信号号(仅在WIFSIGNALED为ture的时候)

      例子:

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <sys/types.h>
     4 #include <signal.h>
     5 
     6 int main()
     7 {
     8     pid_t pid;
     9     int status;
    10     
    11     pid = fork();
    12     if(pid > 0){
    13         printf("Parent process, pid: %d
    ", getpid());
    14         
    15         printf("父进程挂起!
    ");        
    16         pid = wait(&status);
    17         
    18         printf("父进程恢复
    ");
    19         
    20     }else if(pid == 0){
    21         
    22         printf("Child process, pid: %d
    ", getpid());
    23         printf("子进程退出
    ");
    24         exit(status);
    25         
    26     }else{
    27         printf("fork error
    ");
    28     }
    29     
    30     return 0;
    31 }
    View Code

      waitpid是挂起父进程直到某个指定的子进程退出。

    复制代码
    pid_t waitpid( pid_t pid, int *status, int options);
    //pid用来指定进程
    //status保存退出进程状态
    //options有两种选择,WNOHANG/WUNTRACED
    
    /*
    WNOHANG:设定在子进程未退出时也不挂起调用进程,但仅在子进程退出时返回
    WUNTRACED:返回已经停止而且自停止之后还未报告状态的子进程
    */
    复制代码

      pid的取值:

    pid 说明
    >0        挂起直到由pid指定的子进程退出
    0 挂起直到任何一个与调用进程的组ID相同的子进程退出 
    -1 挂起直到任何子进程退出,与wait相同
    <-1 挂起直到任何一个其组ID与pid参数的绝对值相同的子进程退出 

      waitpid增加两个宏:

        WIFSTOPPED: 如果子进程现在已经停止,返回true

        WSTOPSIG:  返回使子进程停止的信号(WIFSTOPPED为true) 

    发送警告信号alarm

      alarm函数在其他函数超时的时候会发送一个SIGALRM信号。

    unsigned it alarm( unsigned int secs );
    //secs为时间,单位为秒
    //如果在超时,未接收到警告信号,返回0,否则,返回等待警告信号的时间

      例子:

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <sys/types.h>
     4 #include <signal.h>
     5 #include <string.h>
     6 
     7 void wakeup(int sig_num)
     8 {
     9     print("timeout quit");
    10     raise(SIGINT);//产生中断信号
    11 }
    12 
    13 int main()
    14 {
    15     int ret;
    16     char buffer[1024];
    17     
    18     signal(SIGALRM, wakeup);
    19     
    20     printf("timeout in 3 seconds, please enter your password
    ");
    21     alarm(3);
    22     
    23     ret = read(0, buffer, 1023);
    24     if(ret >= 0){
    25         buffer[strlen(buffer)-1]='';
    26         printf("password : %s", buffer);
    27     }
    28     
    29     return 0;
    30 }
    View Code

    替换当前进程exec

      exec函数用于完全替换当前进程映像。实际上,就是用一个新的程序来替换当前的进程。值得注意的是,如果替换了,就不可能恢复,是完全的替换。

    复制代码
    int execl( const char *path, const char *arg, ... )
    //path确定要运行的程序
    //其他都为参数
    
    /*
    例子:
       execl( "/bin/ls", "ls", "-la", NULL  );
       //用ls命令程序来替换当前进程  
    */
    复制代码

      例子:

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <stdlib.h>
     4 #include <sys/types.h>
     5 #include <signal.h>
     6 #include <string.h>
     7 
     8 
     9 int main()
    10 {
    11     int status;
    12     char cmd[100];
    13     char* ret;
    14     pid_t pid;
    15     
    16     while(1){
    17         printf("mysh>>");
    18         ret = fgets(cmd, sizeof(cmd), stdin);
    19         if(ret == NULL)
    20             exit(-1);
    21         cmd[strlen(cmd)-1]='';
    22         
    23         if(!strncmp(cmd, "quit", 4))
    24             exit(0);
    25             
    26         pid = fork();
    27         if(pid == 0){
    28             execlp(cmd, cmd, NULL);
    29         }else if(pid >0){
    30             waitpid(pid, &status, 0);
    31         }
    32         printf("
    ");
    33     }
    34 }
    View Code

    参考

    GNU/LINUX环境编程

    http://www.cnblogs.com/avril/archive/2010/03/22/1691793.html

    http://www.ibm.com/developerworks/cn/linux/l-linux-process-management/

    知识共享许可协议作品cococo点点创作,采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行许可。欢迎转载,请注明出处: 转载自:cococo点点 http://www.cnblogs.com/coder2012
     
     
     
    标签: linux c
    1
    0
     
        (请您对文章做出评价)   
     
    « 上一篇:Server Develop (七) Linux  守护进程 » 下一篇:Nginx学习笔记(七) 创建子进程


     
    Add your comment

     

     
    1. #1楼 lugesot 2013-07-08 15:59
      你参考的ibm文章翻译的有问题, /proc/sys/kernel/threads-max 表示的是系统最大线程数,它和stack现在和用户空间内存大小相关。 如果你能找到英文的,帮忙也贴下吧,我是没找到,IBM的链接都乱了
    2. #2楼 lugesot 2013-07-08 16:08
      http://www.ibm.com/developerworks/library/l-linux-process-management/ 好人做到底吧,ulimit -u应该是最大用户进程的限制数。 上面的英文版和中文说的好像也是一致的,我怀疑我自己了,还请高手指点。 也可能和系统版本有关联,一个进程就一个线程,才说的通。
    3. #3楼[楼主] cococo点点 2013-07-08 17:59
      @lugesot LinuxThreads将每个进程的线程最大数目定义为1024,但实际上这个数值还受到整个系统的总进程数限制,这又是由于线程其实是核心进程。 在kernel 2.4.x中,采用一套全新的总进程数计算方法,使得总进程数基本上仅受限于物理内存的大小,计算公式在kernel/fork.c的fork_init()函数中: max_threads = mempages / (THREAD_SIZE/PAGE_SIZE) / 8   参考:http://bbs.chinaunix.net/thread-132343-1-1.html
      内核中对这句代码的标注: /kernel/fork.c         /* * The default maximum number of threads is set to a safe * value: the thread structures can take up at most half * of memory. */
      我是这样理解的,对于Linux来说线程是轻量级的进程,所以一个系统线程的最大数等同于进程的最大数,这个不是一个进程中线程数量的问题。
    4. #4楼 加藤ruby 2013-07-08 19:21
      cococo你写文章的这个布局怎么弄的?尤其是你每个标题的那个背景栏怎么弄的?
    5. #5楼[楼主] cococo点点 2013-07-08 20:23
      @加藤ruby 看CSS 这个也是参考别人 修改的
  • 相关阅读:
    cl编译器命令行调试问题
    西电计算机专业培养
    GCN代码实现
    GCN介绍
    cpu密集型任务及IO密集型任务,GIS,多进程及多线程
    骨架提取
    视频文件的一些属性
    空洞填充
    凸包,二值图家矩形框
    RGB图片取大于阈值部分
  • 原文地址:https://www.cnblogs.com/siguoya/p/3517846.html
Copyright © 2020-2023  润新知