首页 > 代码库 > linux之多线程frok(一)

linux之多线程frok(一)

linux下实现多线程有两种函数调用:一种是通过pthread.h里面已经封装好的函数调用,另一种是通过unistd.h里面的fork函数调用。前面已经已经列举了pthread的使用,下面来书fork的例子。

一.fork函数


简单的fork例子

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
using namespace std;


int main()
{
    pid_t pid;
    pid=fork();
    cout<<"fork()"<<endl;
    return 0;
}
输出:
fork()
fork()

可以看出,从pid=fork()之后开始,后面的输出语句父子进程都会执行一次。
再看例子:

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
using namespace std;


int main()
{
    pid_t pid;
    switch(pid=fork())
    {
        case 0 :cout<<"child:"<<pid<<endl;break;
        case -1:cout<<"error!"<<endl;break;
        default:cout<<"parent:"<<pid<<endl;break;
    }
    return 0;
}

输出:
parent:9743
child:0

fork函数执行成功时会分两次返回pid,其中一次在父进程里面返回,得到的pid是子进程的进程标识符;一次在子进程里面返回,得到的pid是0。所以可以根据pid来区分父子进程,来执行不同的操作。
fork函数执行失败时会返回-1.


二。系统调用 exec


产生子进程之后可以通过exec系列函数执行其他功能程序。

exec系列函数:
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[]);

简单的execl例子
#include <iostream>
#include <unistd.h>
using namespace std;


int main()
{
    cout<<"开始执行"<<endl;
    execl("/bin/date","date","-d","2012/12/12",NULL);
    cout<<"这句不会执行"<<endl;
    return 0;
}

输出:
开始执行
2012年 12月 12日 星期三 00:00:00 CST

execl参数介绍:
第一个参数 path 给出了被执行的程序所在的文件名,它必须是一个有效的路径名,文件本身也必须含有一个真正的可执行程序。
第二个以及用省略号表示的其它参数一起组成了该程序执行时的参数表。
其中,参数表的第一项是不带路径的程序文件名。最后要用一个 null 指针来标记参数表的结尾。


结合fork的例子
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
using namespace std;


int main()
{
    pid_t pid;
    switch(pid=fork())
    {
        case 0 :
        {
            cout<<"child!"<<endl;
            execl("/bin/date","date","-d","2012/12/12",NULL);//执行execl调用
            cout<<"这句不会执行"<<endl;
        }
        case -1:cout<<"error!"<<endl;break;
        default:cout<<"parent!"<<endl;break;
    }
    return 0;
}

输出:
parent!
child!
2012年 12月 12日 星期三 00:00:00 CST

三.父进程与子进程的关联


在fork之后exec之前,两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。所以子进程中的所有变量均保持它们在父进程中之值(fork()的返回值除外)。

当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段会分配单独的物理空间。

文件描述符的继承

在父进程中已打开的文件,在子进程中也已被打开,子进程支持这些文件的文件描述符。但是文件指针则是为两个进程共用

如果设置了执行关闭位(close-on-exec)的话,调用 exec 时会关闭相应的文件。该位的默认值为非设置。

文件指针示例
#include <iostream>
#include <unistd.h>
#include <wait.h>
#include <sys/types.h>
#include <fcntl.h>
using namespace std;


int main()
{
    int fd;
    int pid;
    char buf[1]={‘\0‘};
    if ((fd=open("data.txt",O_RDONLY))<0)
    {//data里面仅有两个字符‘1’和‘2’
        perror("open failed");
        exit(1);
    }
    pid=fork();
    if (pid<0)
        perror("fork failed");
    else if (pid==0)
    {
        read(fd,buf,1);//读取1
        cout<<buf<<endl;
    }
    else
    {
        wait(NULL);//读取2
        read(fd,buf,1);
        cout<<buf<<endl;
    }
}

输出:
1
2

四.进程退出


exit函数

exit()只有一个参数status,称作进程的退出状态,父进程可以使用它的低 8 位。注意,在整型量中,低 8 位在前,高 8 位在后

exit函数示例

#include <iostream>
#include <unistd.h>
#include <wait.h>
#include <sys/types.h>
using namespace std;


int main()
{
    pid_t pid;
    switch(pid=fork())
    {
        case 0 :
        {
            exit(1);//设置退出状态
            break;
        }
        case -1:cout<<"error!"<<endl;break;
        default:
        {
            int statue;
             wait(&statue);
             //获取低8位
             statue=statue>>8;
             statue=statue & 0xff;
             cout<<statue<<endl;
             break;
        }
    }
    return 0;
}

输出
1

五.进程同步


wait函数

当子进程执行时,wait函数可以暂停父进程的执行,使起等待。一旦子进程执行完,等待的父进程就会继续执行。如果有多个子进程在执行,那么父进程中的 wait()在第一个子进程结束时返回,恢复父进程执行。通常情况下,父进程调用 fork()后要调用 wait()。

wait示例
#include <iostream>
#include <unistd.h>
#include <wait.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
using namespace std;


int main()
{
    pid_t pid;
    switch(pid=fork())
    {
        case 0 :
        {
            cout<<"1"<<endl;
            break;
        }
        case -1:cout<<"error!"<<endl;break;
        default:
        {
            switch(pid=fork())
            {
                case 0 :
                {
                    sleep(1);
                    cout<<3<<endl;
                    break;
                }
                case -1:cout<<"error!"<<endl;break;
                default:
			//产生了两个子进程,但在第一个进程返回时,开始执行
                         wait(NULL);
                         cout<<2<<endl;
                         break;
            }
        }
    }
    return 0;
}

输出:
1
2
3

六.关于僵尸进程和孤儿进程


在子进程终止时,根据父进程的状态,子进程有两种不同的状态

1.子进程终止时,父进程并不正在执行 wait()调用。

2.当子进程尚未终止时,父进程却终止了。

第一种状态就是所谓的僵尸进程。处于这种状态的进程不使用任何内核资源,但是要占用内核中的进程处理表那一项。直到其父进程执行wait()操作时,把这种处于僵尸状态的进程从系统内删除,子进程才算真正的退出,而其父进程仍将能得到该子进程的结束状态。

在第二种情况中,一般允许父进程结束时,把它的子进程(包括处于僵尸状态的进程)交归系统的初始化进程所属。

僵尸进程示例
#include <iostream>
#include <unistd.h>
#include <wait.h>
#include <sys/types.h>
using namespace std;


int main()
{
    pid_t pid;
    switch(pid=fork())
    {
        case 0 :
        {
            exit(1);
            break;
        }
        case -1:cout<<"error!"<<endl;break;
        default:
        {
             int statue;
             sleep(10);
             wait(&statue);
             statue=statue>>8;
             statue=statue & 0xff;
             cout<<statue<<endl;
             break;
        }
    }
    return 0;
}

输出
1

这个示例中,子进程退出时,父进程在sleep,如果此时通过ps查看进程执行会发现一项后缀为 <defunct> 的进程,即僵尸进程。
如果把sleep(10)移到子进程,并将父进程的wait(&statue)注释掉,那么子进程将变为孤儿进程。

七.进程属性


进程标识符

通过函数getpid()可以得到当前进程的进程标识符,getppid()可以得到父进程的进程标识符

进程组标识符

如果一个进程组首结束,则该进程组的全部进程都要被强行终止。系统是根据进程的组标识符来选定应该终止的进程的。
比如,当某个用户退出系统时,则相应的 shell 进程所启动的全部进程都要被强行终止。

int setpgrp(void);设置新的进程组标识符
int getpgrp(void);获得其当前的进程组标识符

环境变量

进程的环境是一个以 NULL 字符结尾的字符串之集合。环境中每个字符串形式:name=something

在使用 environ 指针前,应该首先声明它:extern char **environ;

environ示例:
#include <iostream>
#include <unistd.h>
#include <wait.h>
#include <sys/types.h>
using namespace std;
extern char** environ;
int main()
{
    char** env=environ;
    cout<<env[1]<<endl;
    return 0;
}

输出:
USER=root

八.守护进程

int daemon(int nochdir, int noclose);
1. daemon()函数主要用于希望脱离控制台,以守护进程形式在后台运行的程序。
2. 当nochdir为0时,daemon将更改进程的根目录为root(“/”)。
3. 当noclose为0是,daemon将进程的STDIN, STDOUT, STDERR都重定向到/dev/null。
void daemon()
{
	int i:
	pid_t pid;
	if (pid=fork()) exit(0);/* fork,终止父进程 */
	/* 第一子进程 */
	setsid();//创建了一个新的进程组,调用进程成了该进程组的首进程
	signal(SIGHUP,SIG_IGN);
	if (pid=fork()) exit(0);/* fork,终止第一子进程 */
	/* 第二子进程 脱离控制终端*/
	chdir("/");/* 将工作目录设定为"/" */
	umask(0);/* 清除文件掩码 */
	/* 关闭所有文件句柄 */
	for (i=0;i<MAXFD;i++)
	{
		close(i);
	}
}