Linux-创建进程与线程用到的函数解析

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来初始化,之后调用相应的属性设置函数。

  1. pthread_attr_init
    功能:对线程数形变量的初始化
    头文件:<pthread.h>
    函数原型:int pthread_attr_init(pthread_attr_t *arr);
    函数传入值:attr(线程属性)
    函数返回值:成功返回0,失败返回-1
  2. pthread_attr_setscope
    功能:设置线程绑定属性
    头文件:<pthread.h>
    函数原型:int pthread_attr_setscope(pthread_attr_t *attr, int scope);
    函数传入值:attr(线程属性);scope:PTHREAD_SCOPE_SYSTEM(绑定),PTHREAD_SCOPE_PROCESS(非绑定)
    函数返回值:同1
  3. pthread_attr_setdetachstate
    功能:设置线程分离属性
    头文件:<pthread.h>
    函数原型:int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
    函数传入值:attr(线程属性),detachstate:PTHREAD_CREATE_DETACHED(分离),PTHREAD_CREATE_JOINABLE(非分离)
    函数返回值:同1
  4. pthread_attr_getschedparam
    功能: 得到线程优先级。
    头文件: <pthread.h>
    函数原型: int pthread_attr_getschedparam (pthread_attr_t* attr, struct sched_param* param);
    函数传入值:attr:线程属性;
    param:线程优先级;
    函数返回值:同1
  5. 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中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容