Linux-创建进程与线程用到的函数解析
【1】exit:
- exit函数可以退出程序并将控制权返回给操作系统,而用return语句可以从一个函数中返回给调用该函数的函数。如果在main()函数中加入return语句,那么在执行这条语句后将退出main()函数并将控制权返回给操作系统,这样的一条return语句与exit函数作用是相同的。
下面通过一个程序测试以下在子函数中使用exit是否会直接在子函数中就把整个程序终止了:
exitExample.c
/*
测试exit函数与return函数的不同
*/
#include <stdio.h>
#include <stdlib.h>
void exitExample();
void exitExample()
{
int i = 0;
if(i==0)
exit(0);
}
int main()
{
// 调用一个函数,测试其用exit是否还会返回到main函数
exitExample();
printf("看来在调用的函数中使用exit也能返回到调用这个函数的父函数来.\n");
return 0;
}
the result:
yangruo@Y700:~/workspace/processTrain/shiyan1$ ./exitExample
yangruo@Y700:~/workspace/processTrain/shiyan1$
发现确实是直接结束了,说明推测是正确的!
新的问题又来了:如果是子进程呢?会不会直接退出整个程序?
/*
*测试在子进程中使用exit,是否会直接退出整个程序
*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
void childFunc();
void childFunc()
{
printf("这是子进程,他的PID是%d, 他的父进程PID是%d.\n", getpid(), getppid());
exit(0);
}
int main()
{
printf("这是main函数,也就是1号进程,他的PID是%d,他的父进程PID是%d.\n", getpid(), getppid());
// 创建一个子进程
pid_t child;
child = fork();
if(child == 0){
// 说明创建子进程成功
// 子进程执行一个任务
childFunc();
}else if(child < 0){
puts("进程创建失败\n");
exit(1);
}else if(child > 0){
printf("真的会输出吗?\n");
printf("如果输出了的话就打印PID瞧瞧是哪个进程!\n");
printf("现在这个进程PID是%d.\n", getpid());
}
/*
发现一个有趣的问题:若直接是else,而不是else if(child < 0),那么else里面的语句依旧会被输出,后来找到原因:
这是逻辑有问题,直接else的话,程序不知道这是在说child的其他值。
!!!
NO,测试了else if(child > 0),发现里面的语句还是会输出!所以这并不是逻辑出错了,if else语句是知道指的是child
这个变量的!
哦哦!原来fork()是个神奇的函数!他仅仅被调用了一次,却能返回两次,更多见注释【2】fork
*/
// 父进程等待子进程退出
pid_t cpid = wait(NULL); // 【5】
printf("子进程%d已经退出.\n", cpid);
// 父进程自己退出
printf("父进程%d退出,程序终止\n", getpid());
return 0;
}
- 现在说说exit函数的返回值,即exit(int status)的status是什么意思:
- “C 语言的设计之初就是为 Unix 系统设计的,而这个系统是『很多程序互相配合』搭配成一个系统。每个运行着的程序都是进程,而进程就会有父进程,父进程通常是直接启动你的进程,父进程死亡的进程会被 init 收养,其父进程变为 init,而 init 的父进程是进程 0,进程 0 则是系统启动时启动的第一个进程。”【引用自知乎用户pansz】
- exit()里的参数,是传递给父进程的。对父进程来说,你的进程仿佛是一个函数,而函数可以有返回值。通常一个程序的父进程可能是任何进程,因此我们无法语气我们的父进程是否规定必须要有这个返回值,那么我们应当提供这个返回值,以保证不同的父进程的需求得到满足。
- 返回值“0”表示没有错误,程序已经正常执行完毕,非0值表示有错误发生,至于非0值具体为多少由开发者自己定义,比如1表示输入错误,2表示计算错误之类,也有一些是由系统定义的错误代码,比如栈溢出,除零之类的错误。
- 为什么要使用exit呢?
是历史原因,虽然现在大多数平台下,直接在 main() 函数里面 return 可以退出程序。但是在某些平台下,在 main() 函数里面 return 会导致程序永远不退出(因为代码已经执行完毕,程序却还没有收到要退出的指令)。换句话说,为了兼容性考虑,在特定的平台下,程序最后一行必须使用 exit() 才能正常退出,这是 exit() 存在的重要价值。
【2】fork
- 一个进程,包括代码、数据、分配给进程的资源。fork()通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
- 一个进程调用fork()后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的进程中,只有少数值和原来的进程不同,相当于克隆了一个自己。
- fork()调用的奇妙之处在于它仅仅被调用一次,却能够返回两次,它有可能有三种不同返回值:
- 在父进程中,fork返回新创建子进程的进程PID;
- 在子进程中,fork返回0;
- 如果出现错误,fork返回一个负值。
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork返回0,父进程返回新创建子进程的进程ID。
用Strace
命令跟踪了一个简单的 fork 程序,发现 fork 其实调用的是clone
函数:
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fa8e369f9d0) = 3252
【3】pthread_create()第二个参数
pthread_create()的第二个参数是设置线程的属性,这些属性主要包括绑定属性(SCS、PCS)、分离属性、堆栈地址、堆栈大小、优先级。其中系统默认是非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。若把第二个参数设置为NULL的话,将采用系统默认的属性配置。
- 绑定属性
在Linux中,采用的是“一对一”的线程机制,即一个用户线程对应一个内核线程。绑定属性就是指一个用户线程固定地分配给一个内核线程,因为CPU时间片的调度是面向内核线程(轻量级进程)的,因此具有绑定熟悉的线程可以保证在需要的时候总有一个内核线程与之对应,而与之对应的非绑定属性就是指用户线程和内核线程的关系不是始终固定的,而是由系统来分配。 - 分离属性
分离属性是决定以一个什么样的方式来终止自己。在非分离状况下,当一个线程结束时,它多占用的线程没有得到释放,也就是没有真正的终止,需要通过pthread_join()来释放资源。而在分离属性下,一个线程结束就会立即释放它所占有的系统资源。但这里有一点要注意的是,如果设置一个线程分离属性,而这个线程又运行得非常快的话,那么它可能在pthread_create函数返回之前就终止了线程函数的运行,它终止以后就很可能将线程号和系统资源移交给其他的线程使用,这时调用pthread_create的线程就得到错误的线程号。
上面的属性都是通过一些函数完成的,通常先调用pthread_attr_init来初始化,之后调用相应的属性设置函数。
- pthread_attr_init
功能:对线程数形变量的初始化
头文件:<pthread.h>
函数原型:int pthread_attr_init(pthread_attr_t *arr);
函数传入值:attr(线程属性)
函数返回值:成功返回0,失败返回-1 - pthread_attr_setscope
功能:设置线程绑定属性
头文件:<pthread.h>
函数原型:int pthread_attr_setscope(pthread_attr_t *attr, int scope);
函数传入值:attr(线程属性);scope:PTHREAD_SCOPE_SYSTEM(绑定),PTHREAD_SCOPE_PROCESS(非绑定)
函数返回值:同1 - pthread_attr_setdetachstate
功能:设置线程分离属性
头文件:<pthread.h>
函数原型:int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
函数传入值:attr(线程属性),detachstate:PTHREAD_CREATE_DETACHED(分离),PTHREAD_CREATE_JOINABLE(非分离)
函数返回值:同1 - pthread_attr_getschedparam
功能: 得到线程优先级。
头文件: <pthread.h>
函数原型: int pthread_attr_getschedparam (pthread_attr_t* attr, struct sched_param* param);
函数传入值:attr:线程属性;
param:线程优先级;
函数返回值:同1 - pthread_attr_setschedparam
功能: 设置线程优先级。
头文件: <pthread.h>
函数原型: int pthread_attr_setschedparam (pthread_attr_t* attr, struct sched_param* param);
函数传入值:attr:线程属性。
param:线程优先级。
函数返回值:同1
pthAttrExa.c
/*测试线程属性*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *pthread_func_1();
void *pthread_func_2();
void *pthread_func_1()
{
int i = 0;
for(; i<6; i++)
{
printf("This is pthread_1.\n");
if(i == 2)
{
pthread_exit(0);
}
}
}
void *pthread_func_2()
{
int i = 0;
for(; i<3; i++)
{
printf("This is pthread_2.\n");
}
}
int main()
{
pthread_t pt_1 = 0;
pthread_t pt_2 = 0;
pthread_attr_t attr = {0};
int ret = 0;
pthread_attr_init(&attr); // 属社设置
//pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
ret = pthread_create(&pt_1, &attr, pthread_func_1, NULL);
if(ret != 0)
{
printf("创建线程失败\n");
}
ret = pthread_create(&pt_2, NULL, pthread_func_2, NULL);
if(ret != 0)
{
printf("创建线程失败\n");
}
int ret2;
ret2 = pthread_join(pt_2, NULL);
if(ret2 == 0)
printf("线程2已经结束\n");
else
printf("线程2没有结束,错误号%d", ret2);
int ret1;
ret1 = pthread_join(pt_1, NULL);
if(ret1 == 0)
printf("线程1已经结束\n");
else
printf("线程1没有结束,错误号%d\n", ret1);
return 0;
}
the result:
$ ./pthAttrExaThis is pthread_1.
This is pthread_1.
This is pthread_1.
This is pthread_2.
This is pthread_2.
This is pthread_2.
线程2已经结束
线程1没有结束,错误号22
【4】pthread_join()
函数原型:int pthread_join(pthread_t thread, void **retval);
头文件:<pthread.h>
参数:
- thread:线程标识符,即线程ID
- retval:用户定义的指针,用来存储被等待线程的返回值
返回值:0代表成功,失败返回错误号
功能:以阻塞的方式等待thread指定的线程结束,当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回,并且thread指定的线程必须是JOINABLE的。
注意:
- 一个可“join”的线程所占用的内存仅当有线程执行pthread_join()后才会释放,因此为了避免内存泄露,所有线程终止时,要么已被设为DETACHED,要么用pthread_join()来回收资源。
- 一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join()的线程返回错误代码ESRCH。
【4`】pthread_exit()
函数原型:void pthread_exit(void *retval);
头文件:<pthread.h>
参数:
retval:pthread_exit()调用线程的返回值,可以用pthread_join()函数来检索获取。(只有当线程状态是非分离属性时才能正常得到这个值)
功能:退出线程。
注意:
- 线程终止最重要的问题是资源释放的问题,特别是临界资源的释放。因为临界资源在一段时间内只能被一个线程所持有,当线程要使用临界资源时需提交请求,如果该资源未被使用则申请成功,否则等待。临界资源使用完毕后要释放以便其他线程可以使用。
- 线程终止的另外一个问题是线程间的同步问题。一般情况下,进程中各个线程的运行是相互独立的,线程的终止并不会相互通知,也不会影响其他的线程,终止的线程所占用的资源不会随着线程的终止而归还系统,而是仍为线程所在的进程持有。正如进程之间可以使用wait()系统调用来等待其他进程结束一样,线程也有类似的函数:pthread_join()。
ptrExitJoin.c
#include<stdio.h>
#include<pthread.h>
void assisthread(void*arg)
{
printf("I am helping to do some jods\n");
//sleep(3);
printf("pthreadID:%lu\n",pthread_self());
//pthread_exit(0);
}
int main()
{
pthread_t assistthid;
int status = 0;
// pthread_attr_t attr = {0};
// pthread_attr_init(&attr); // 属社设置
// pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
printf("main pthreadID:%lu\n",pthread_self());
// pthread_create(&assistthid,&attr,(void*)assisthread,NULL);
pthread_create(&assistthid,NULL,(void*)assisthread,NULL);
printf("create pthreadID:%lu\n",assistthid);
pthread_join(assistthid,(void*)&status);//等待线程assisthread结束
printf("assistthid's exit is caused %d\n",status);
return 0;
}
【5】wait
头文件:<sys/types.h> <sys/wait.h>
函数原型:pid_t wait(int *status)
函数说明:wait()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数status 返回, 而子进程的进程识别码也会一快返回. 如果不在意结束状态值, 则参数 status 可以设成NULL. 子进程的结束状态值请参考waitpid().
传入值:status是一个整型变量指针,是该子进程退出的状态。若status不为NULL,则通过它可以获得子进程的结束状态。另外,子进程的结束状态可由Linux中一些特殊的宏来测试。
返回值:如果执行成功则返回子进程识别码(PID),如果有错误返回-1,失败原因在errno中。