进程的创建

windows下创建进程

CreateProcess函数简介

windows下想要创建一个子进程不如linux的fork函数来得方便,通过CreateProcess函数创建一个新的进程,函数的定义如下

BOOL CreateProcess(  
 LPCTSTR lpApplicationName, // 应用程序名称  
 LPTSTR lpCommandLine, // 命令行字符串  
 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程的安全属性  
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性  
 BOOL bInheritHandles, // 是否继承父进程的属性  
 DWORD dwCreationFlags, // 创建标志  
 LPVOID lpEnvironment, // 指向新的环境块的指针  
 LPCTSTR lpCurrentDirectory, // 指向当前目录名的指针  
 LPSTARTUPINFO lpStartupInfo, // 传递给新进程的信息  
 LPPROCESS_INFORMATION lpProcessInformation // 新进程返回的信息  
);  


该函数用来创建一个新的进程。

第 1 个参数 lpApplicationName 是输入参数,指向启动进程的 exe 文件。

第 2 个参数 lpCommandLine 是输入参数,是启动进程的命令行中的参数。

当这两个参数都不为 NULL 时,第 1 个参数指定要启动的进程 exe 文件(不带参数),第 2 个参数指定启动进程所需参数。第 1 个参数也可以为 NULL,此时第 2 个参数就不能为 NULL,在 lpCommandLine 需要指定出要启动的程序名以及所接参数,彼此间以空格隔开,其中第 1 个参数即是程序名。

第 3 个参数 lpProcessAttributes 是输入参数,指向 SECURITY_ATTRIBUTES 结构变量,是进程的安全属性,可以为 NULL 则使用默认的安全属性。

第 4 个参数 lpThreadAttributes 是输入参数,同第 3 个参数一样,指向 SECURITY_ATTRIBUTES 结构变量。

第 5个参数 bInheritHandles 是输入参数,表示新进程是否从调用进程处继承了句柄。如果参数的值为 TRUE,调用进程中的每一个可继承的打开句柄都将被子进程继承。被继承的句柄与原进程拥有完全相同的值和访问权限;如果设为 FALSE,那么不继承。

第 6 个参数 dwCreationFlags 是输入参数,表示进程的创建标志以及优先级控制。如 :CREATE_NEW_CONSOLE 会使新建的控制台程序拥有一个新的控制台;DEBUG_PROCESS 调用进程将被当作一个调试程序,并且新进程会被当作被调试的进程。系统把被调试程序发生的所有调试事件通知给调试器。

第 7 个参数 lpEnvironment 是输入参数,指向新进程的环境变量块,如果设置为 NULL,那么使用父进程的环境变量。

第 8 个参数 lpCurrentDirectory 是输入参数,指定创建后新进程的当前目录,如果设置为 NULL,那么就在父进程所在的当前目录。

第 9 个参数 lpStartupInfo 是输入参数,指向一个 STARTUPINFO 结构,该结构里可以设定启动信息,可以设置为 NULL 。

第 10 个参数 lpProcessInformation 是输出参数,指向一个 PROCESS_INFORMATION 结构,返回被创建进程的信息。
 

示例:

#include "stdafx.h"
#include 
#include 
 
int_tmain(intargc, _TCHAR* argv[])
{
 
    PROCESS_INFORMATION ProInfo;    //进程信息结构
 
    STARTUPINFO    StartInfo;
    ZeroMemory ( &StartInfo,sizeof(StartInfo));
 
        LPTSTR  szPrameter = TEXT("C:\\Users\\Administrator\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe [url]www.groad.net[/url]");
    TCHARszCmdLine[2048] = {0};
    CopyMemory(szCmdLine, szPrameter, 2*_tcslen(szPrameter));
 
        ZeroMemory (&ProInfo,sizeof(ProInfo));
 
 if(!CreateProcess ( NULL,        // 执行的程序名
            szCmdLine,                    // 命令行指定
            NULL,                        // 进程安全属性,NULL 时使用默认安全属性
            NULL,                        // 线程安全属性,NULL 时使用默认安全属性
            FALSE,                        // 不继承句柄
            0,                            // 进程创建标志
            NULL,                        // 环境变量块,为 NULL 时使用父进程环境变量
            NULL,                    // 新进程目录
            &StartInfo,                        // 启动信息结构
            &ProInfo)                    // 进程信息结构
        ) {
            _tprintf (TEXT("CreateProcess failed : %d\n"), GetLastError());
            return(-1);
    }
 
    // 等待子进程结束
    WaitForSingleObject(ProInfo.hProcess, INFINITE);
                     
    CloseHandle ( ProInfo.hProcess );
    CloseHandle ( ProInfo.hThread );
 
    return0;
}


上面程序启动谷歌 Chrome 浏览器并打开 www.groad.net 这个主页。在程序中,当第 1 个参数为 NULL 时,要启动的程序以及网址参数均指定在第 2 个参数中。注意,不能直接将参数 szPrameter 直接填写到第 2 个参数中,因为指定的命令行参数中含有空格,这样往往会造成参数解析错误,比如它会被解析成:

C:\\Users\\Administrator\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe www.groad.net.exe

也就是会认为 chrome.exe www.groad.net.exe 这就是一个程序,这是无法执行的。

而将命令行参数拷贝到数组中却是可行的,但如此一来却要多定义一个数组。如果是英文 Windows 环境,并无需构建多字符程序,那么可以将引号把路径括起来,这也不会出错,比如:

CreateProcess (NULL, "\"Path to exe\" -p1-p2 -p3", ...);

注意上面的路径使用反斜杠转义了括起路径的双引号。

但是,如果在第 1 个参数和第 2 个参数里分别指定程序名和参数,那么也无需添加一个数组来存储命令行参数,但第 2 个参数中指定的命令行参数注意前面要添加一个空格,否则可能无法正确解析参数,比如:

LPTSTR szPname = TEXT("D:\\Program Files (x86)\\Maxthon3\\Bin\\Maxthon.exe");
LPTSTR szPrameter = TEXT("www.groad.net");     //网址前无空格,错误。正确的形式是添加一个空格

这里只是针对浏览器打开网址这种情况,并非所有的情形都会没问题,比如我测试时,IE 和 MaxThon 是需要在网址面前添加空格的,否则打开的是一个空白页。但对于 Chrome ,不管网址前加不加空格都只能打开空白页,得用上面添加的数组情况方能正常。这也许和浏览器程序本身有关。如果用的是记事打开一些文本,如果后面的文本路径参数不添加空格,那么打开空白,反之正常;而对于 UE ,那么即使后面不添加空格也照样打开,所以从这两个例子看来,仍然是和程序本身有关。但是,添加一个空格会保持较好的兼容性,如若还不行,可以考虑用一个数组来接纳所有的命令行参数。
 

另外,程序中使用了 WaitForSingleObject() 函数以等待子进程的退出,如当我们关闭了浏览器,那么上面的程序也随之结束,否则一直在那等待。

linux下创建进程

初识linux操作系统,fork作为系统调用理解起来却并不是很容易。
整理一下学习笔记,希望能对后来的初学者有所帮助。

代码能说明问题

#include
#include
int main()
{
    pid_t pid;
    int count = 0;
    pid = fork();   //fork一个进程
    if(pid == 0)
    {               //pid为0,
        printf("this is child process, pid is %d\n",getpid());//getpid返回的是当前进程的PID
        count+=2;
        printf("count = %d\n",count);
    }
    else if(pid > 0)
    {
        printf("this is father process, pid is %d\n",getpid());
        count++;
        printf("count = %d\n",count);
    }
    else
    {
        fprintf(stderr,"ERROR:fork() failed!\n");
    }
    return 0;
}

接下来问题就来了
fork的时候发生什么?
①执行到这一句的时候,一个进程被创建了,这个进程与父进程一样,拥有一套与父进程相同的变量,相同的一套代码,这里可以粗浅的理解为子进程又复制了一份main函数。这里返回一个子进程的进程号,大于0。(第一次fork)

②子进程怎么执行:

子进程从fork()的位置开始执行,也就是说前面的代码不走,但是拥有之前的变量以及变量的值,与父进程的值一样,这次fork(),返回值是0,所以在子进程里面直接执行了pid==0这一个分支,父进程里面并不执行这个分支的语句。这就为我们在写mian函数的时候怎么写子进程的程序提供了一个方法来隔离代码。


明白了这个原理之后我们再来看一段代码

#include
#include
int main()
{
    pid_t pid[3];
    int count = 0;
    pid[0] = fork();
    pid[1] = fork();
    pid[2] = fork();

    printf("this is process\n");

    return 0;
}

运行结果

这里写图片描述

这里每一次输出表示一个进程的创建,可以看到一共有8个进程被创建,有兴趣的话可以验证一下连续四次fork可以出16个进程,但是不建议再多了,电脑会卡死,不要问我怎么知道的!
猜想是出2的n次方个进程。如果上面的第一段代码理解了的话,我们按照子进程从父进程fork的位置开始执行就会理解为什么会有八个进程。

这里附上思维导图助于理解
这里写图片描述


那么我们想创建不是2的n次方个进程应该怎么做呢?这里还是以三个为例

#include 
#include 
#include 

int main(int argc, char *argv[])  {
    int i,j,status;
    int pid[3];

    for(i=0; i<3;i++){
        if((pid[i]=fork()) >0){

            printf("This is child process pid=%d\n",pid[i]);

        } else{
            printf("This is father process pid=%d\n",pid[i]);
            exit( EXIT_SUCCESS);
        }

    }

    return EXIT_SUCCESS;
}

这里给出一个参考,不是最准确的,可以与三次fork进行对比

转载地址:linux下连续三次fork() --深度理解进程创建函数

你可能感兴趣的:(linux,c/c++,windows,c/c++)