zoukankan      html  css  js  c++  java
  • [linux]进程(四)——进程的创建

    11,进程的创建

    linux的进程创建可以分为两个步骤,分别为fork()和exec()函数,fork()负责创建一个子进程,和父进程的差别仅仅是PID PPID以及一些统计量,exec()函数负责读取可执行文件载入地址空间运行。


    fork()函数原型

    pid_t fork(void); 子进程返回0,父进程返回子进程的PID,fork()函数一次创建两次返回。


    fork()函数的实现
    fork()采用了写时拷贝(copy-on-write)的技术,刚创建子进程的时候父进程和子进程拥有相同的地址空间(这些区域被设定为只读),只有在子进程或者父进程要写入数据的时候,父进程相关的地址空间才会被拷贝,父子进程指向相同的物理内存。
    fork()之后是父进程先执行还是子进程先执行是不确定的,通过fork()函数创建的父子进程是共享一个文件表项的,linux通过clone()系统调用实现fork()函数,
    另外一个创建子进程的方法是调用vfork()函数,vfork()和fork()函数的区别是vfork()函数不拷贝父进程的页表,子进程在父进程的地址空间先运行,直到子进程退出后,父进程才可以继续运行,另外vfork()函数有一个隐患就是一旦子进程调用exec()函数执行失败。。。。。

    fork()函数示例:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #include <stdio.h>  
    2. #include <signal.h>  
    3. #include <stdlib.h>  
    4. #include <string.h>  
    5. #include <unistd.h>  
    6. #include <errno.h>  
    7. int glob = 5;  
    8. char buf[] ="a write to stdout ";  
    9. int main(void)  
    10. {  
    11.     int var;  
    12.     pid_t pid;  
    13.     var = 88;  
    14.     if(write(STDOUT_FILENO,buf,(sizeof(buf)-1)) != (sizeof(buf)-1))  
    15.         printf("write error ");  
    16.     printf("before fork ");  
    17.     if((pid = fork()) < 0)  
    18.     {     
    19.         printf("fork error ");  
    20.     }  
    21.     else if (pid == 0)  
    22.     {  
    23.         glob++;  
    24.         var++;  
    25.     }  
    26.     else  
    27.     {  
    28.         sleep(2);  
    29.     }  
    30.     printf("pid = %d,glob = %d, var = %d ",getpid(),glob,var);  
    31.     exit(0);  
    32. }  
    33. 输出如下:  
    34. ./a.out  
    35. a write to stdout  
    36. before fork  
    37. pid = 430,glob = 7, var = 89    
    38. pid = 429,glob = 6, var = 88  
    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. vfork()示例程序如下:  
    2. #include <stdio.h>  
    3. #include <signal.h>  
    4. #include <stdlib.h>  
    5. #include <string.h>  
    6. #include <unistd.h>  
    7. #include <errno.h>  
    8. int glob = 5;  
    9. char buf[] ="a write to stdout ";  
    10. int main(void)  
    11. {  
    12.     int var;  
    13.     pid_t pid;  
    14.     var = 88;  
    15.     if(write(STDOUT_FILENO,buf,(sizeof(buf)-1)) != (sizeof(buf)-1))  
    16.         printf("write error ");  
    17.     printf("before fork ");  
    18.     if((pid = vfork()) < 0)  
    19.     {     
    20.         printf("fork error ");  
    21.     }  
    22.     else if (pid == 0)  
    23.     {  
    24.         glob++;  
    25.         var++;  
    26.     //  exit(0);  
    27.     }  
    28.     else  
    29.     {  
    30.         sleep(2);  
    31.     }  
    32.     printf("pid = %d,glob = %d, var = %d ",getpid(),glob,var);  
    33.     exit(0);  
    34. }  
    35. 函数输出如下:  
    36. ./a.out  
    37. before fork  
    38. pid = 29039,glob = 7,var =89  
    39. 子进程对变量做了加1操作结果改变了父进程中变量的值,因为子进程与父进程的地址空间相同,  

    父子进程通信的实例程序如下:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. #define RECOVERY_API_VERSION 2.3.1  
    2. const char* binary = "/tmp/update_binary";  
    3. int pipefd[2];  
    4. pipe [pipefd];  
    5. const char** args = (const char**)malloc(sizeof(char*)*5);  
    6. args[0]=binary;  
    7. args[1]=RECOVERY_API_VERSION;  
    8. char *temp = (char*)malloc(10);  
    9. sprintf(temp,"%d",pipefd[1]);  
    10. args[2]=temp;  
    11. args[3]=(char*)path;  
    12. args[4]=NULL;  
    13. pid_t pid = fork();  
    14. if(pid == 0)  
    15. {  
    16.     close(pipefd[1]);  
    17.     execv(binary,(char*const*)args));  
    18.     printf(exec child process error);  
    19.     _exit(-1);  
    20. }  
    21. close(pipefd[1]);  
    22. char buffer[1024];  
    23. FILE* from_child = fdopen(pipefd[0],"r");  
    24. while(fgets(buffer,sizeof(buffer),from_child)!=NULL)  
    25.     char*command = strtok(buffer," ");  
    26. waitpid(pid,&status,0)  


    fork()函数系统调用

    fork()函数系统调用的入口点是sys_fork()函数,最终是调用内核的do_fork()函数(该函数是与体系结构无关的函数)

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. asmlinkage int sysfork()  
    2. {  
    3.     return do_fork(SIGCHLD,regs.regs->ARM_sp,®s,NULL,NULL)  
    4. }  

    由以上代码可以知道,子进程结束后会发送SIGCHLD信号通知父进程。

    do_fork()函数主要调用了copy_process()函数,copy_process()函数返回一个新的task_struct结构体,copy_process()函数会调用dup_task_struct()函数,此时创建的子

    进程和原本的父进程只有一个参数不一样,即task_struct->stack,stack通常和thread_info一起保存在一个联合中,

    进程环境:
    以C语言的main()函数为例:main()函数的原型如下:
    int main(int argc,int *argv[]);
    C程序的存储空间布局
    正文段 BSS段 初始化数据段 栈 堆

    补充:exec族函数

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. extern char **environ;  
    2. int execl(const char *path, const char *arg, ...);  
    3. int execlp(const char *file, const char *arg, ...);  
    4. int execle(const char *path, const char *arg, ..., char * const envp[]);  
    5. int execv(const char *path, char *const argv[]);  
    6. int execvp(const char *file, char *const argv[]);  
    7. int execve(const char *filename, char *const argv[], char *const envp[]);  
    8. 示例程序如下:  
    9. 例子  
    10. #include <stdio.h>  
    11. #include <unistd.h>  
    12. int main()  
    13. {  
    14.     execl("/bin/ls","ls","-l",NULL);  
    15.    
    16.     printf("如果execl执行失败,这个就会打印出来了 ");  
    17.     return 1;  
    18. }  



    12,内核线程
     12,内核线程
    内核经常需要在后台执行一些操作,内核线程,它是独立运行在内核空间的标准进程,内核线程和普通的进程间的区别在于内核线程没有独立的地址空间(实际上它的mm指针被设置为NULL)。它们只在内核空间运行,从来不切换到用户空间去,内核进程和普通进程一样,可以被调度,也可以被抢占,Linux确实会将一些任务交给内核线程去做,像pdflush和ksoftirqd这些任务就是明显的例子,内核线程也只能由其他的内核线程创建,一般情况下,内核线程会将它在创建时得到的函数永远的执行下去,除非系统重启。

    内核线程的创建:
    pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
    从现有的内核线程创建一个新的内核线程的方法如下:
    struct task_struct *kthread_creat(int (*threadfn)(void *data),void *data,const char namefmt[]....);
    新的线程将运行threadfn函数,给其传递的参数为data,线程名为namefmt,
    新创建的线程处于不可运行的状态,要通过wake_up_process()或者以下函数使其运行
    struct task_struct *kthread_run(int (*threadfn)(void *data),void *data,const char namefmt[]....)

    内核线程的退出:
    内核线程启动后一直运行直到调用do_exit()函数,或者调用如下函数int kthread_stop(struct task_struct *k),传递给kthread_stop的参数是kthread_creat()函数返回的task_struct的地址


    实例:
    创建线程

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. struct task_struct *thread_task;  
    2. int rc;  
    3. thread_task=kthread_create(fsg_main_thread,common,"file-storage");  
    4. if(IS_ERR(thread_task))  
    5. {  
    6.     rc = PTR_ERR(thread_task);  
    7.     return ERR_PTR(rc);  
    8. }  
    9. wake_up_process(thread_task);  

    13,进程退出

    通过正常的进程结束、通过信号或是通过对exit函数的调用,不管进程如何退出,进程的结束都要借助对内核函数do_exit(在alps/kernel/kernel/exit.c内)的调用,进程的资源回收和进程描述符的回收是分开的,进程描述符是在其父进程的wait()函数返回后收回~

    补充:UNIX环境进程异常退出的情况
     以下是参考以下网址的博客:http://www.ibm.com/developerworks/cn/aix/library/1206_xiejd_unixexception/
    进程异常大致可以分为两类:
    一:向进程发送信号导致进程异常退出
    二:代码本身错误导致进程退出
    第一类:向进程发送信号导致进程退出:
    (1)向进程发送信号导致进程退出,进程收到信号后可能导致进程退出并产生coredump文件,在 UNIX环境中有三种方式将信号发送给目标进程,导致进程异常退出。
     方式一:调用函数kill()发送信号,原型是 int kill(pid_t pid,int sig)
      以下代码为示例代码:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1.     1 #include <sys/types.h>   
    2.  2 #include <signal.h>   
    3.  3   
    4.  4 int main(int argc, char* argv[])   
    5.  5 {   
    6.  6     char* pid = argv[1];   
    7.  7     int PID = atoi(pid);   
    8.  8   
    9.  9     kill(PID, SIGSEGV);   
    10. 10     return 0;   
    11. 11 }  


     方式二:运行kill命令发送信号
      格式为 kill SIGXXX PID
     方式三:在终端使用键盘发送信号
      使用 control-C 发送 SIGINT 信号,使用 control- 发送 SIGQUIT 信号,使用 control-z 发送 SIGTSTP 信号,如何防止这类信号到来导致信号退出?通过调用 signal 函数绑定信号处理程序来应对信号的到来 void (*signal(int sig, void (*func)(int)))(int);插入下面的代码,以达到屏蔽信号 SIGINT 的效果  (void)signal(SIGINT, SIG_IGN);
    第二类:编程错误导致进程异常退出
     当进程执行非法操作时,计算机会抛出处理器异常,异常是指 soft interrupts,是进程非法操作所导致的处理器异常, 这类异常是进程执行非法操作所产生的同步异常,比如内存保护异常,除 0 异常,缺页异常等等。系统为每个异常都分配了异常处理函数,当有相应的异常的时候就会去调用相应的异常处理函数。
     实例分析:
     (1)内存非法访问实例

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1.    1 #include<stdio.h>   
    2. int main()   
    3. 3 {   
    4. 4      char* str = "hello";   
    5. 5      str[0] = 'H';   
    6. 6      return 0;   
    7. 7 }   


     (2)除0异常

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. 1 #include <stdio.h>   
    2.  2   
    3.  3 int main()   
    4.  4 {   
    5.  5     int a = 1, b = 0, c;   
    6.  6     printf( "Start running " );   
    7.  7     c = a/b ;   
    8.  8     printf( "About to quit " );   
    9.  9 }   


    对于进程异常的问题应该如何调试
     (1)利用coredump文件分析异常退出的原因

  • 相关阅读:
    开源项目中标准文件命名和实践
    linux远程拷贝命令-scp
    Linux访问Windows共享目录的方法——smbclient
    ADB Fix error : insufficient permissions for device
    APT典型应用示例
    我的参考书籍列表
    GCC Reference
    GNU make简介
    Windows下搭建Android NDK开发环境及命令行编译
    Git命令行基本操作
  • 原文地址:https://www.cnblogs.com/zhiliao112/p/4051363.html
Copyright © 2011-2022 走看看