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()函数示例:
- #include <stdio.h>
- #include <signal.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <errno.h>
- int glob = 5;
- char buf[] ="a write to stdout ";
- int main(void)
- {
- int var;
- pid_t pid;
- var = 88;
- if(write(STDOUT_FILENO,buf,(sizeof(buf)-1)) != (sizeof(buf)-1))
- printf("write error ");
- printf("before fork ");
- if((pid = fork()) < 0)
- {
- printf("fork error ");
- }
- else if (pid == 0)
- {
- glob++;
- var++;
- }
- else
- {
- sleep(2);
- }
- printf("pid = %d,glob = %d, var = %d ",getpid(),glob,var);
- exit(0);
- }
- 输出如下:
- ./a.out
- a write to stdout
- before fork
- pid = 430,glob = 7, var = 89
- pid = 429,glob = 6, var = 88
- vfork()示例程序如下:
- #include <stdio.h>
- #include <signal.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <errno.h>
- int glob = 5;
- char buf[] ="a write to stdout ";
- int main(void)
- {
- int var;
- pid_t pid;
- var = 88;
- if(write(STDOUT_FILENO,buf,(sizeof(buf)-1)) != (sizeof(buf)-1))
- printf("write error ");
- printf("before fork ");
- if((pid = vfork()) < 0)
- {
- printf("fork error ");
- }
- else if (pid == 0)
- {
- glob++;
- var++;
- // exit(0);
- }
- else
- {
- sleep(2);
- }
- printf("pid = %d,glob = %d, var = %d ",getpid(),glob,var);
- exit(0);
- }
- 函数输出如下:
- ./a.out
- before fork
- pid = 29039,glob = 7,var =89
- 子进程对变量做了加1操作结果改变了父进程中变量的值,因为子进程与父进程的地址空间相同,
父子进程通信的实例程序如下:
- #define RECOVERY_API_VERSION 2.3.1
- const char* binary = "/tmp/update_binary";
- int pipefd[2];
- pipe [pipefd];
- const char** args = (const char**)malloc(sizeof(char*)*5);
- args[0]=binary;
- args[1]=RECOVERY_API_VERSION;
- char *temp = (char*)malloc(10);
- sprintf(temp,"%d",pipefd[1]);
- args[2]=temp;
- args[3]=(char*)path;
- args[4]=NULL;
- pid_t pid = fork();
- if(pid == 0)
- {
- close(pipefd[1]);
- execv(binary,(char*const*)args));
- printf(exec child process error);
- _exit(-1);
- }
- close(pipefd[1]);
- char buffer[1024];
- FILE* from_child = fdopen(pipefd[0],"r");
- while(fgets(buffer,sizeof(buffer),from_child)!=NULL)
- char*command = strtok(buffer," ");
- waitpid(pid,&status,0)
fork()函数系统调用
fork()函数系统调用的入口点是sys_fork()函数,最终是调用内核的do_fork()函数(该函数是与体系结构无关的函数)
- asmlinkage int sysfork()
- {
- return do_fork(SIGCHLD,regs.regs->ARM_sp,®s,NULL,NULL)
- }
由以上代码可以知道,子进程结束后会发送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族函数
- extern char **environ;
- int execl(const char *path, const char *arg, ...);
- int execlp(const char *file, const char *arg, ...);
- int execle(const char *path, const char *arg, ..., char * const envp[]);
- int execv(const char *path, char *const argv[]);
- int execvp(const char *file, char *const argv[]);
- int execve(const char *filename, char *const argv[], char *const envp[]);
- 示例程序如下:
- 例子
- #include <stdio.h>
- #include <unistd.h>
- int main()
- {
- execl("/bin/ls","ls","-l",NULL);
- printf("如果execl执行失败,这个就会打印出来了 ");
- return 1;
- }
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的地址
实例:
创建线程
- struct task_struct *thread_task;
- int rc;
- thread_task=kthread_create(fsg_main_thread,common,"file-storage");
- if(IS_ERR(thread_task))
- {
- rc = PTR_ERR(thread_task);
- return ERR_PTR(rc);
- }
- 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)
以下代码为示例代码:
- 1 #include <sys/types.h>
- 2 #include <signal.h>
- 3
- 4 int main(int argc, char* argv[])
- 5 {
- 6 char* pid = argv[1];
- 7 int PID = atoi(pid);
- 8
- 9 kill(PID, SIGSEGV);
- 10 return 0;
- 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)内存非法访问实例
- 1 #include<stdio.h>
- 2 int main()
- 3 {
- 4 char* str = "hello";
- 5 str[0] = 'H';
- 6 return 0;
- 7 }
(2)除0异常
- 1 #include <stdio.h>
- 2
- 3 int main()
- 4 {
- 5 int a = 1, b = 0, c;
- 6 printf( "Start running " );
- 7 c = a/b ;
- 8 printf( "About to quit " );
- 9 }
对于进程异常的问题应该如何调试
(1)利用coredump文件分析异常退出的原因