p { margin-bottom: 0.25cm; line-height: 120% }

(一) 认识进程

在Linux系统中,每一个进程都有自己的ID,就如同人的身份证一样。linux中有一个数据类pid_t。 该数据用来定义进程的ID。其实就是一个非负的整数

进程的状态:运行态,等待态,结束态,就绪,挂起和僵尸状态。进程就是在这几个状态间来回切换。

首先来看下如何创建新的进程,这里需要用到fork函数。使用fork函数需要用到<sys/types.h>和<unistd.h>头文件,函数的返回类型为pid_t。表示一个非负的整数。代码如下

void get_pid()
{
    pid_t pid;
    if ((pid=fork())<0)
    {
        printf("fork error\n");
        exit(1);
    }
    else if(pid == 0)
    {
        printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
        printf("in the child process!\n");
    }
    else
    {
        printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
        printf("in the parent process!\n");
    }
    exit(0);
}

p { margin-bottom: 0.25cm; line-height: 120% }

执行结果:

the pid is 3738in the parent process!

the pid is 0in the child process!

从结果可以看到返回了两条打印,分别是父进程和子进程。原因在于调用fork函数后,就会出现分叉。在子进程中fork函数返回0, 在父进程中,fork函数返回子进程的ID。也就是fork函数会复制一个进程给子进程。为什么pid的值在父子进程中返回的值不一样呢。原因在于父子进程相当与链表,进程形成了链表,父亲进程的pid意味这指向子进程的id,但是子进程没有id,所以其pid=0

这样fork函数就是调用一次,返回两次。在开发过程中,可以根据返回值的不同,对父进程和子进程执行不同的代码。我们来把代码变动下来看下父进程和子进程的关系。增加了一个全局变量gvar和局部变量var。

int gvar=2;

void get_pid()
{
    pid_t pid;
    int var=5;
    if ((pid=fork())<0)
    {
        printf("fork error\n");
        exit(1);
    }
    else if(pid == 0)
    {
        gvar--;
        var++;
        printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
        printf("in the child process!\n");
    }
    else
    {
        printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
        printf("in the parent process!\n");
    }
    exit(0);
}

p { margin-bottom: 0.25cm; line-height: 120% }

运行结果,在子进程中,对gvar和var分别进行减一和加一的操作。但是在父进程中,gvar和var仍然是原值且并没有被改动。原因在于fork函数胡复制父进程的所有资源,包括内存等。因此父子进程属于不同的内存空间。当修改的时候自然没有同步。

the pid is 4694

, gvar=2,var=5

in the parent process!

the pid is 0

, gvar=1,var=6

in the child process!

除了fork外,还有一个vfork函数,和fork一样都是系统调用函数。两者的区别在于vfork在创建子进程的时候不会复制父进程的所有资源,父子进程共享地址空间。因此子进程中修改的所有变量在父进程也会被修改,因为同属一个地址空间。

int get_pid__vfork()
{
    pid_t pid;
    int var =5;
    printf("process id:%d\n",getpid());
    printf("gvar=%d var=%d\n",gvar,var);
    if((pid=vfork())<0)
    {
        printf("error");
        return 1;
    }
    else if(pid == 0)
    {
        gvar--;
        var++;
        printf("The child process id:%d\n gvar=%d var=%d\n",getpid(),gvar,var);
        exit(0);
    }
    else
    {
        printf("the pareent process id:%d\n gvar=%d var=%d\n",getpid(),gvar,var);
        return 0;
    }
}

p { margin-bottom: 0.25cm; line-height: 120% }

执行结果:

process id:4821

gvar=2 var=5

The child process id:4822

gvar=1 var=6

the pareent process id:4821

gvar=1 var=6

在fork和vfork中,子进程和父进程都是运行同样的代码。那么如果想让子进程执行不同的操作。就需要用到execv函数

在test1.c中的代码:

p { margin-bottom: 0.25cm; line-height: 120% }

void main(int argc,char* argv[])

{

execve("test2",argv,environ);

}

test2.c中的代码

int main()

{

puts("welcome!");

return 0;

}

p { margin-bottom: 0.25cm; line-height: 120% }

首先编译这2个文件,得到两个可执行的文件test1和test2。然后运行./test1. 得到的结果如下:test1.c中执行了test2.c的代码。

root@zhf-linux:/home/zhf/zhf/c_prj# ./test1

welcome!

(二)进程等待:

进程等待就是为了同步父进程和子进程。等待调用wait函数,wait函数的工作原理是首先判断子进程是否存在,如果创建失败,子进程不存在,则会直接退出进程。并且提示相关错误信息。如果创建成功,wait函数会将父进程挂起,直到子进程结束,并且返回结束时的状态和最后结束的子进程PID。来看下具体的代码实现。

oid exit_s(int status)
{
    if(WIFEXITED(status)){
        printf("normal exist,status=%d\n",WEXITSTATUS(status));
    }
    else if(WIFSIGNALED(status)){
        printf("signal exit!status=%d\n",WTERMSIG(status));
    }
}

void wait_function_test()
{
    pid_t pid,pid1;
    int status;
    int ret;
    int pr;
    if((pid=fork())<0)
    {
        printf("child process error!\n");
        exit(0);
    }
    else if(pid == 0)
    {
        printf("the pid of child is %d\n",getpid());
        printf("The child process\n");
        sleep(3);
        exit(2);
    }
    else
    {
        printf("the pid of parent is %d\n",getpid());
        printf("I am waiting for child process to exit\n");
        pr=wait(&status);
        if (pr > 0){
            printf("I catched a child process with pid of %d\n",pr);
        }
        exit_s(status);
    }
}

p { margin-bottom: 0.25cm; line-height: 120% }

在子进程中调用sleep(3)睡眠3秒钟。只有子进程从睡眠中苏醒过来,才能正常退出并被父进程捕捉到。在此期间父进程会继续等待下去。在wait函数中会将子进程的状态保存在status中

在 exit_s中调用了几个宏:

WIFEXITED:当子进程正常退出时,返回真值

WEXITSTATUS:返回子进程正常退出时的状态。只有当WIFEXITED为真值的时候。

WTERMSIG:用于子进程被信号终止的情况。返回此信号类型

执行结果如下,可以看到在父进程等到3秒后,子进程开始退出。然后被父进程捕获到。

the pid of parent is 5063

I am waiting for child process to exit

the pid of child is 5064

The child process

I catched a child process with pid of 5064

normal exist,status=2

那如果子进程是异常退出的,会是什么样的情况呢。

void wait_function_test_for_childerror()
{
    pid_t pid,pid1;
    int status;
    int ret;
    int pr;
    if((pid=fork())<0)
    {
        printf("child process error!\n");
        exit(0);
    }
    else if(pid == 0)
    {
        pid1=getpid();
        printf("the pid of child is %d\n",pid1);
        printf("The child process\n");
        kill(pid1,9);
    }
    else
    {
        printf("the pid of parent is %d\n",getpid());
        printf("I am waiting for child process to exit\n");
        pr=wait(&status);
        printf("the pid is %d\n",pid);
        printf("the pr value is %d\n",pr);
        if (pr != pid)
        {
            printf("parent process wait error\n");
        }
        exit_s(status);
    }
}

p { margin-bottom: 0.25cm; line-height: 120% }

在子进程中用kill(pid,9)的方法来杀死进程。其中kill的用法参考kill -l命令。结果如下。

1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP

6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1

11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM

16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP

21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ

26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR

31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3

38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8

43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13

48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12

53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7

58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2

63) SIGRTMAX-1 64) SIGRTMAX

执行结果如下:可以看到不再是normal exit而是signal exit,并且状态码也从2变成了9

I am waiting for child process to exit

the pid of child is 5218

The child process

I catched a child process with pid of 5218

signal exit!status=9

(三)线程

首先来看下线程和进程的区别。

进程是资源分配的最小单位,每个进程都是有独立的内存空间

线程是程序执行的最小单位。多个线程共享同一个内存空间。

一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源。

总的来说就是进程有独立的地址空间,线程没有单独的地址空间,同一进程内的所有线程共享内存空间

那么线程相对于进程的优势是什么呢:

1
线程不需要额外的内存申请

2
线程共享进程内的数据,访问数据方便。而进程则需要通过通信的方式进行

进程使用ps的方式查看,线程则可以通过top的方式进行查看。还可以通过top
-p pid的方式进行某个进程内的线程查看。

p { margin-bottom: 0.25cm; line-height: 120% }

来看一个创建线程的例子:

void *mythread1(void)
{
    int i;
    for(i=0;i<5;i++)
    {
        printf("This is the first thread\n");
        sleep(1);
    }
}

void *mythread2(void)
{
    int i;
    for(i=0;i<5;i++)
    {
        printf("This is the second thread\n");
        sleep(1);
    }
}

void main(int argc,char* argv[])
{
    int ret=0;
    pthread_t id1,id2;
    pthread_create(&id1,NULL,(void *)mythread1,NULL);
    pthread_create(&id2,NULL,(void *)mythread2,NULL);
    pthread_join(id1,NULL);
    pthread_join(id2,NULL);
}

p { margin-bottom: 0.25cm; line-height: 120% }

mythread1和mythread2两个函数分别属于不同的线程。pthread_t 是线程的id.。pthread_create是创建线程的函数。其中的第一个为线程id。第二个参数指线程的属性,这里设置位空。第三个参数:线程运行函数的起始地址。第四个参数:运行函数的参数。这里也设置位空。

但是在用gcc调试的时候出现如下错误,提示找不到线程的相关函数。原因在于pthread的库不是Linux系统的库,所以在进行编译的时候要加上-lpthread,否则编译不过

root@zhf-linux:/home/zhf/zhf/c_prj#
gcc -g -o test1 test1.c

/tmp/ccH40hx1.o:
In function `main':

/home/zhf/zhf/c_prj/test1.c:188:
undefined reference to `pthread_create'

/home/zhf/zhf/c_prj/test1.c:189:
undefined reference to `pthread_create'

/home/zhf/zhf/c_prj/test1.c:190:
undefined reference to `pthread_join'

/home/zhf/zhf/c_prj/test1.c:191:
undefined reference to `pthread_join'

换成如下就可以正常编译了:

root@zhf-linux:/home/zhf/zhf/c_prj#
gcc -o test1 test1.c -lpthread

root@zhf-linux:/home/zhf/zhf/c_prj#
./test1

执行结果如下:两个线程在交替的运行

This
is the first thread

This
is the second thread

This
is the second thread

This
is the first thread

This
is the first thread

This
is the second thread

This
is the first thread

This
is the second thread

This
is the first thread

This
is the second thread

线程还有很多其他的操作,比如变量互斥,锁,同步,信号等,讲在后面的章节中详细介绍。

linux c编程:初识进程与线程的更多相关文章

  1. python并发编程之进程、线程、协程的调度原理(六)

    进程.线程和协程的调度和运行原理总结. 系列文章 python并发编程之threading线程(一) python并发编程之multiprocessing进程(二) python并发编程之asynci ...

  2. linux高级编程基础系列:线程间通信

    linux高级编程基础系列:线程间通信 转载:原文地址http://blog.163.com/jimking_2010/blog/static/1716015352013102510748824/ 线 ...

  3. (转)如何在Linux中统计一个进程的线程数

    如何在Linux中统计一个进程的线程数 原文:http://os.51cto.com/art/201509/491728.htm 我正在运行一个程序,它在运行时会派生出多个线程.我想知道程序在运行时会 ...

  4. 【Linux 应用编程】进程管理 - 进程、线程和程序

    基本概念 程序和进程的区别 程序是平台相关的二进制文件,只占用磁盘空间.编写完程序代码后,编译为可执行的二进制文件即可. 进程是运行中的程序,占用 CPU.内存等系统资源. 通过 Shell 命令,可 ...

  5. Linux多线程编程,为什么要使用线程,使用线程的理由和优点等

    线程?为什么有了进程还需要线程呢,他们有什么区别?使用线程有什么优势呢?还有多线程编程的一些细节问题,(http://www.0830120.com)如线程之间怎样同步.互斥,这些东西将在本文中介绍. ...

  6. python 多线程编程之进程和线程基础概念

    多线程编程 在多线程(multithreaded,MT)出现之前,计算机程序的执行都是由单个步骤序列组成的,该序列组合在主机的CPU中按照同步顺序执行.无论是任务本身需要按照步骤顺序执行,还是整个过程 ...

  7. Java并发编程:进程和线程之由来

    Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...

  8. java并发编程:进程和线程

    java并发编程涉及到很多内容,当然也包括多线程,再次补充点相关概念 原文地址:http://www.cnblogs.com/dolphin0520/p/3910667.html 一.操作系统中为什么 ...

  9. Java并发编程:进程和线程之由来__进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能

    转载自海子:http://www.cnblogs.com/dolphin0520/p/3910667.html Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨 ...

  10. linux系统编程之进程(六):父进程查询子进程的退出,wait,waitpid

    本节目标: 僵进程 SIGCHLD wait waitpid 一,僵尸进程 当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止. ...

随机推荐

  1. CVE-2016-3714 - ImageMagick 命令执行

    ImageMagick是一款使用量很广的图片处理程序,很多厂商都调用了这个程序进行图片处理,包括图片的伸缩.切割.水印.格式转换等等.但近来有研究者发现,当用户传入一个包含『畸形内容』的图片的时候,就 ...

  2. excel下拉级联的做法

    前面的文章讲了,excel下拉级联,重新选第一个下拉,后面那个值怎么清除.今天我讲下excel利用宏解决整个表格的级联下拉问题. 我遇到的情况是两个下垃圾连,第一个医生类别,第二个医生职称,而且我是要 ...

  3. TASKCTL产品功能清单-转载

    功能分类 功能描述 一级 二级 关系 调度控制 作业依赖关系调度 作业依赖关系调度是调度最基本的功能,指作业间具有顺序的运行,比如:a.b.c三个作业,只有当a完成后才运行b,b完成才能运行c 作业并 ...

  4. HTTP和HTTPS有什么区别? 什么是SSL证书?使用ssl证书优势?

    什么是SSL? SSL是指安全套接层协议(以及传输层协议TLS),位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持,是目前使用最广泛的安全协议.它为互联网或内部网络连接,进行操作的两台 ...

  5. WPF个人助手更新

    大家好,这次更新主要是去除一些无关的功能,界面做了很大的调整,以前都是自己写的 UI ,最近也引入了 WPF-UI ,挺不错的,特此表示感谢,也希望大家会喜欢,别的也就不多说了,本软件以实用性为主,采 ...

  6. RabbitMQ 使用(一)

    RabbitMQ中的使用 这篇文章将会介绍关于RabbbitMQ的使用,并且使用的是kombo(客户端的Python实现)来实现: 安装 如果使用的是mac安装的话,可以先安装到指定的位置,接着配置命 ...

  7. 1.three.js世界的4大要素

    一.三大组件 在Three.js中,要渲染物体到网页中,我们需要3个组建:场景(scene).相机(camera)和渲染器(renderer).有了这三样东西,才能将物体渲染到网页中去. 记住关建语句 ...

  8. master log 与relay log的关系

    --master log 与relay log的关系 -------------------------------2014/06/09 Just to clarify, there are thre ...

  9. 字符串拼接data-id时注意事项

    今天测试下一个ajax请求,结果后台接收不到data-id的数据,导致后台无法进行正确的数据库查询. 我的评论页面是使用字符串拼接后,再放到页面里的,其中有关data-id的部分是这样的: '< ...

  10. iOS的阴影绘制及性能优化

    今天来讲讲iOS开发过程中的阴影绘制及其潜在的绘图性能问题.虽然在开发过程中,我们使用阴影功能的机会不是很多,但是如果用了,有可能引起如卡顿等性能问题,所以,还是有必要来探究一下阴影的绘制过程,及如何 ...