踩叶轻飞吧 关注:4贴子:145
  • 6回复贴,共1

Linux进程与线程

只看楼主收藏回复



1楼2015-04-13 18:25回复
    使用fork()创建进程:
    Linux中1号用户态进程init,由内核态运行的init()函数产生。用户态其他所有进程都是init的子进程。
    fork 创造的子进程复制了父亲进程的资源,包括内存的内容task_struct内容,新旧进程使用同一代码段,复制数据段和堆栈段,这里的复制采用了注明的copy_on_write技术,即一旦子进程开始运行,则新旧进程的地址空间已经分开,两者运行独立。
    如:
    int main() {
    int num = 1;
    int child;
    if(!(child = fork())) {
    printf("This is son, his num is: %d. and his pid is: %d\n", ++num, getpid());
    } else {
    printf("This is father, his num is: %d, his pid is: %d\n", num, getpid());
    }
    }
    执行结果为:This is son, his num is: 2. and his pid is: 2139
    This is father, his num is: 1, his pid is: 2138
    从代码里面可以看出2者的pid不同,子进程改变了num的值,而父进程中的num没有改变。
    总结:优点是子进程的执行独立于父进程,具有良好的并发性。缺点是两者的通信需要专门的通信机制,如pipe、fifo和system V等。有人认为这 样大批量的复制会导致执行效率过低。其实在复制过程中,子进程复制了父进程的task_struct,系统堆栈空间和页面表TLB,在子进程运行前,两者指向同一页面。而当子进程改变了父进程的变量时候,会通过copy_on_write的手 段为所涉及的页面建立一个新的副本。因此fork效率并不低。


    3楼2015-04-13 18:56
    回复
      使用vfork()创建进程:
      vfork函数创建的子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间任何数据的修改都为父进程所见。这与fork是完全不同的,fork进程是独立的空间。另外一点不同的是vfork创建的子进程后,父进程会被阻塞,直到子进程执行exec()和exit()。如:
      int main() {
      int num = 1;
      int child;
      if(!(child = fork())) {
      printf("This is son, his num is: %d. and his pid is: %d\n", ++num, getpid());
      } else {
      printf("This is father, his num is: %d, his pid is: %d\n", num, getpid());
      }
      }
      运行结果为:This is son, his num is: 2. and his pid is:4139
      This is father, his num is: 2, his pid is: 4138
      从运行结果可以看到vfork创建出的子进程(线程)共享了父进程的num变量,这一次是指针复制,2者的指针指向了同一个内存。
      总结:当创建子进程的目的仅仅是为了调用exec()执行另一个程序时,子进程不会对父进程的地址空间又任何引用。因此,此时对地址空间的复制是多余的,通过vfork可以减少不必要的开销。


      4楼2015-04-13 18:58
      回复
        使用clone()创建进程:
        函数功能强大,带了众多参数,因此由他创建的进程要比前面2种方法要复杂。clone可以让你有选择性的继承父进程的资源,你可以选择想vfork一样和 父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);这里fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本", child_stack明显是为子进程分配系统堆栈空 间(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的 值),flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数)。


        5楼2015-04-13 19:02
        回复
          扩展:
          系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。整个linux系统的所有进程也是一个树形结构。树根是系统自动构造的,即在内核态下执行的0号进程,它是所有进程的祖先。由0号进程创建1号进程(内核态),1号负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟主存管理的内核线程。随后,1号进程调用execve()运行可执行程序init,并演变成用户态1号进程,即init进程。它按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号、2号...的若干终端注册进程getty。每个getty进程设置其进程组标识号,并监视配置到系统终端的接口线路。当检测到来自终端的连接信号时,getty进程将通过函数execve()执行注册程序login,此时用户就可输入注册名和密码进入登录过程,如果成功,由login程序再通过函数execv()执行shell,该shell进程接收getty进程的pid,取代原来的getty进程。再由shell直接或间接地产生其他进程。
          上述过程可描述为:0号进程->1号内核进程->1号内核线程->1号用户进程(init进程)->getty进程->shell进程
          注意,上述过程描述中提到:1号内核进程调用执行init()并演变成1号用户态进程(init进程),这里前者是init是函数,后者是进程。
          两者容易混淆,区别如下:
          1.init()函数在内核态运行,是内核代码。
          2.init进程是内核启动并运行的第一个用户进程,运行在用户态下。
          3.init()函数调用execve()从文件/etc/inittab中加载可执行程序init并执行,这个过程并没有使用调用do_fork(),因此两个进程都是1号进程。
          fork,vfork,clone都是linux的系统调用,用来创建子进程的。
          -------------------------
          子进程的创建是基于父进程的,因此一直追溯上去,总有一个进程是原始的,即没有父进程的。这个进程在Linux中的进程号是0,也就是传说中的0号进程(可惜很多理论书上对这个重要的进程只字不提)。
          如果说子进程可以通过规范的创建进程的函数(如:fork())基于父进程复制创建,那么0号进程并没有可以复制和参考的对象,也就是说0号进程拥有的所有信息和资源都是强制设置的,不是复制的,这个过程我称为手工设置,也就是说0号进程是“纯手工打造”,这是操作系统中“最原始”的一个进程,它是一个模子,后面的任何进程都是基于0号进程生成的。
          手工打造0号进程最主要包括两个部分:创建进程0运行时所需的所有信息,即填充0号进程,让它充满“血肉”;二是调度0号进程的执行,即让它“动”起来,只有动起来,才是真正意义上的进程,因为进程本身实际上是个动态的概念。


          6楼2015-04-13 19:06
          回复
            Linux守护进程(daemon)的创建过程:
            创建守护进的关键步骤:
            step 1.创建子进程,父进程退出
            step 2.在子进程中创建新会话
            step 3.改变当前目录为根目录
            step 4.重设文件权限掩码
            step 5.关闭文件描述符


            10楼2015-04-13 19:41
            回复
              7.取消一个线程
              有时,我们想让一个线程可以要求另一个线程终止,线程有方法做到这一点,与信号处理一样,线程可以在被要求终止时改变其行为。
              先来看用于请求一个线程终止的函数:
              #include <pthread.h>
              int pthread_cancel(pthread_t thread);
              这个函数简单易懂,提供一个线程标识符,我们就可以发送请求来取消它。
              线程可以用pthread_setcancelstate设置自己的取消状态。
              #include <pthread.h>
              int pthread_setcancelstate(int state, int *oldstate);
              参数说明:
              state:可以是PTHREAD_CANCEL_ENABLE允许线程接收取消请求,也可以是PTHREAD_CANCEL_DISABLE忽略取消请求。
              oldstate:获取先前的取消状态。如果对它没兴趣,可以简单地设置为NULL。如果取消请求被接受了,线程可以进入第二个控制层次,用pthread_setcanceltype设置取消类型。
              #include <pthread.h>
              int pthread_setcanceltype(int type, int *oldtype);
              参数说明:
              type:可以取PTHREAD_CANCEL_ASYNCHRONOUS,它将使得在接收到取消请求后立即采取行动;另一个是PTHREAD_CANCEL_DEFERRED,它将使得在接收到取消请求后,一直等待直到线程执行了下述函数之一后才采取行动:pthread_join、pthread_cond_wait、pthread_cond_timedwait、pthread_testcancel、sem_wait或sigwait。
              oldtype:允许保存先前的状态,如果不想知道先前的状态,可以传递NULL。
              默认情况下,线程在启动时的取消状态为PTHREAD_CANCEL_ENABLE,取消类型是PTHREAD_CANCEL_DEFERRED。
              下面编写代码thread6.c,主线程向它创建的线程发送一个取消请求。
              #include <pthread.h>
              #include <stdio.h>
              #include <stdlib.h>
              void *thread_function(void *arg);
              int main()
              {
              int res;
              pthread_t a_thread;
              void *thread_result;
              res = pthread_create(&a_thread, NULL, thread_function, NULL);
              if (res != 0)
              {
              perror("Thread create failed!");
              exit(EXIT_FAILURE);
              }
              sleep(4);
              printf("Canceling thread.../n");
              res = pthread_cancel(a_thread);
              if (res != 0)
              {
              perror("Thread cancel failed!");
              exit(EXIT_FAILURE);
              }
              printf ("Waiting for thread to finished.../n");
              res = pthread_join(a_thread, &thread_result);
              if (res != 0)
              {
              perror ("Thread join failed!");
              exit(EXIT_FAILURE);
              }
              printf("Thread canceled!");
              exit(EXIT_FAILURE);
              }
              void *thread_function(void *arg)
              {
              int i;
              int res;
              res = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
              if (res != 0)
              {
              perror("Thread setcancelstate failed!");
              exit(EXIT_FAILURE);
              }
              res = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
              if (res != 0)
              {
              perror("Thread setcanceltype failed!");
              exit(EXIT_FAILURE);
              }
              printf("thread_function is running.../n");
              for (i = 0; i < 10; i++)
              {
              printf("Thread is still running (%d).../n", i);
              sleep(1);
              }
              pthread_exit(0);
              }
              编译这个程序:
              gcc -D_REENTRANT thread6.c -o thread6 –lpthread
              运行这个程序:
              $ ./thread6
              thread_function is running...
              Thread is still running (0)...
              Thread is still running (1)...
              Thread is still running (2)...
              Thread is still running (3)...
              Canceling thread...
              Waiting for thread to finished...
              8.多线程
              之前,我们所编写的代码里面都仅仅是创建了一个线程,现在我们来演示一下如何创建一个多线程的程序。
              #include <pthread.h>
              #include <stdio.h>
              #include <stdlib.h>
              #define NUM 6
              void *thread_function(void *arg);
              int main()
              {
              int res;
              pthread_t a_thread[NUM];
              void *thread_result;
              int index;
              for (index = 0; index < NUM; ++index) {
              res = pthread_create(&a_thread[index], NULL, thread_function, (void *)index);
              if (res != 0)
              {
              perror("Thread create failed!");
              exit(EXIT_FAILURE);
              }
              sleep(1);
              }
              printf("Waiting for threads to finished.../n");
              for (index = NUM - 1; index >= 0; --index)
              {
              res = pthread_join(a_thread[index], &thread_result);
              if (res == 0)
              {
              printf("Picked up a thread:%d/n", index + 1);
              }
              else
              {
              perror("pthread_join failed/n");
              }
              }
              printf("All done/n");
              exit(EXIT_SUCCESS);
              }
              void *thread_function(void *arg)
              {
              int my_number = (int)arg;
              int rand_num;
              printf("thread_function is running. Argument was %d/n", my_number);
              rand_num = 1 + (int)(9.0 * rand()/(RAND_MAX + 1.0));
              sleep(rand_num);
              printf("Bye from %d/n", my_number);
              pthread_exit(NULL);
              }
              编译这个程序:
              gcc -D_REENTRANT thread7.c -o thread7 –lpthread
              运行这个程序:
              $ ./thread7
              thread_function is running. Argument was 0
              thread_function is running. Argument was 1
              thread_function is running. Argument was 2
              thread_function is running. Argument was 3
              thread_function is running. Argument was 4
              Bye from 1
              thread_function is running. Argument was 5
              Waiting for threads to finished...
              Bye from 5
              Picked up a thread:6
              Bye from 0
              Bye from 2
              Bye from 3
              Bye from 4
              Picked up a thread:5
              Picked up a thread:4
              Picked up a thread:3
              Picked up a thread:2
              Picked up a thread:1
              All done
              9.小结
              本文主要介绍了Linux环境下的多线程编程,介绍了信号量和互斥量、线程属性控制、线程同步、线程终止、取消线程及多线程并发。
              本文比较简单,只作为初学Linux多线程编程入门之用。


              15楼2015-04-13 20:37
              回复