1. 程式人生 > >linux與Windows程序控制

linux與Windows程序控制

程序管理控制

這裡實現的是一個自定義timer用於統計子程序執行的時間。使用方式主要是

timer [-t seconds] command arguments

例如要統計ls的執行時間可以直接輸入timer ls,其後的arguments是指所要執行的程式的引數。如:timer ls -al。如果要指定程式執行多少時間,如5秒鐘,可以輸入timer -t 5 ls -al。需要注意的是,該程式對輸入沒有做異常檢測,所以要確保程式輸入正確。

Linux

程式思路

  1. 獲取時間

    時間獲取函式使用gettimeofday,精度可以達到微秒

    struct timeval{
         long tv_sec;*//秒*
         long tv_usec;*//微秒*
    }
  2. 子程序建立

    1. fork()函式

      #include <sys/types.h>
      #include <unistd.h>
      pid_t fork(void);

      fork呼叫失敗則返回-1,呼叫成功則:

      fork函式會有兩種返回值,一是為0,一是為正整數。若為0,則說明當前程序為子程序;若為正整數,則該程序為父程序且該值為子程序pid。關於程序控制的詳細說明請參考:程序控制

    2. exec函式

      用fork建立子程序後執行的是和父程序相同的程式(但有可能執行不同的程式碼分支),子程序往往要呼叫一種exec函式以執行另一個程式。當程序呼叫一種exec函式時,該程序的使用者空間程式碼和資料完全被新程式替換,從新程式的啟動例程開始執行。呼叫exec並不建立新程序,所以呼叫exec前後該程序的id並未改變。

      其實有六種以exec開頭的函式,統稱exec函式:

      #include <unistd.h>
      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[]);
      int execve(const char *path, char *const argv[], char *const envp[]);

      這些函式如果呼叫成功則載入新的程式從啟動程式碼開始執行,不再返回,如果調用出錯則返回-1,所以exec函式只有出錯的返回值而沒有成功的返回值。

    3. waitwaitpid

      一個程序在終止時會關閉所有檔案描述符,釋放在使用者空間分配的記憶體,但它的PCB還保留著,核心在其中儲存了一些資訊:如果是正常終止則儲存著退出狀態,如果是異常終止則儲存著導致該程序終止的訊號是哪個。這個程序的父程序可以呼叫wait或waitpid獲取這些資訊,然後徹底清除掉這個程序。我們知道一個程序的退出狀態可以在Shell中用特殊變數$?檢視,因為Shell是它的父程序,當它終止時Shell呼叫wait或waitpid得到它的退出狀態同時徹底清除掉這個程序。
      如果一個程序已經終止,但是它的父程序尚未呼叫wait或waitpid對它進行清理,這時的程序狀態稱為殭屍(Zombie)程序。任何程序在剛終止時都是殭屍程序,正常情況下,殭屍程序都立刻被父程序清理了。
      殭屍程序是不能用kill命令清除掉的,因為kill命令只是用來終止程序的,而殭屍程序已經終止了。

    #include <sys/types.h>
    #include <sys/wait.h>
    pid_t wait(int *status);
    pid_t waitpid(pid_t pid, int *status, int options);

    若呼叫成功則返回清理掉的子程序id,若調用出錯則返回-1。父程序呼叫wait或waitpid時可能會:

    • 阻塞(如果它的所有子程序都還在執行

    • 帶子程序的終止資訊立即返回(如果一個子程序已終止,正等待父程序讀取其終止資訊)
    • 出錯立即返回(如果它沒有任何子程序)

    這兩個函式的區別是:

    • 如果父程序的所有子程序都還在執行,呼叫wait將使父程序阻塞,而呼叫waitpid時如果在options引數中指定WNOHANG可以使父程序不阻塞而立即返回0
    • wait等待第一個終止的子程序,而waitpid可以通過pid引數指定等待哪一個子程序

原始碼

timer原始碼

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <wait.h>
#include <ctime>
#include <iostream>
#include <cstring>
//程式假定輸入完全正確,沒有做異常處理
//mytime [-t number] 程式
using namespace std;
//呼叫系統時間
struct timeval time_start;
struct timeval time_end;

void printTime();

void newProcess(const char *child_process, char *argv[], double duration);

int main(int argc, char const *argv[])
{
    double duration = 0;
    char **arg;
    int step = 2;
    if (argc > 3 && (strcmp((char *)"-t", argv[1]) == 0)) //如果指定了執行時間
    {
        step = 4;
        duration = atof(argv[2]); //沒有做異常處理
    }

    arg = new char *[argc - step + 1];
    for (int i = 0; i < argc - step; i++)
    {
        arg[i] = new char[100];
        strcpy(arg[i], argv[i + step]);
    }
    arg[argc - step] = NULL;

    newProcess(argv[step - 1], arg, duration);
    return 0;
}

void printTime()
{
    //用以記錄程序執行的時間
    int time_use = 0;  // us
    int time_left = 0; // us
    int time_hour = 0, time_min = 0, time_sec = 0, time_ms = 0, time_us = 0;
    gettimeofday(&time_end, NULL);

    time_use = (time_end.tv_sec - time_start.tv_sec) * 1000000 + (time_end.tv_usec - time_start.tv_usec);
    time_hour = time_use / (60 * 60 * (int)pow(10, 6));
    time_left = time_use % (60 * 60 * (int)pow(10, 6));
    time_min = time_left / (60 * (int)pow(10, 6));
    time_left %= (60 * (int)pow(10, 6));
    time_sec = time_left / ((int)pow(10, 6));
    time_left %= ((int)pow(10, 6));
    time_ms = time_left / 1000;
    time_left %= 1000;
    time_us = time_left;
    printf("此程式執行的時間為:%d 小時, %d 分鐘, %d 秒, %d 毫秒, %d 微秒\n", time_hour, time_min, time_sec, time_ms, time_us);
}

void newProcess(const char* child_process, char **argv, double duration)
{
    pid_t pid = fork();
    if (pid < 0) //出錯
    {
        printf("建立子程序失敗!");
        exit(1);
    }
    if (pid == 0) //子程序
    {
        execvp(child_process, argv);
    }
    else
    {
        if (abs(duration - 0) < 1e-6)
        {
            gettimeofday(&time_start, NULL);
            wait(NULL); //等待子程序結束
            printTime();
        }
        else
        {
            gettimeofday(&time_start, NULL);
            // printf("sleep: %lf\n", duration);
            waitpid(pid, NULL, WNOHANG);
            usleep(duration * 1000000); // sec to usec
            int kill_ret_val = kill(pid, SIGKILL);
            if (kill_ret_val == -1) // return -1, fail
            {
                printf("kill failed.\n");
                perror("kill");
            }
            else if (kill_ret_val == 0) // return 0, success
            {
                printf("process %d has been killed\n", pid);
            }
            printTime();
        }
    }
}

測試原始碼

#include <iostream>
#include <ctime>
#include <unistd.h>
using namespace std;
int main(int argc, char const *argv[])
{
    for(int n = 0; n < argc; n++)
    {
        printf("arg[%d]:%s\n",n, argv[n]);
    }
    sleep(5);
    return 0;
}

測試

  1. 自行編寫程式測試

  2. 系統程式測試

  3. 將timer加入環境變數

    這裡僅進行了臨時變數修改。

Windows

在Windows下進行父子程序的建立和管理在api呼叫上相較Linux有一定難度,但實際上在使用管理上比Linux容易的多。

CreateProcess

#include <Windows.h>
BOOL CreateProcessA(
  LPCSTR                lpApplicationName,
  LPSTR                 lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCSTR                lpCurrentDirectory,
  LPSTARTUPINFOA        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

原始碼實現

timer程式

// 程序管理.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//

#include <iostream>
#include <wchar.h>
#include <Windows.h>
#include <tchar.h>
using namespace std;


void printTime(SYSTEMTIME* start, SYSTEMTIME* end);
void newProcess(TCHAR* cWinDir, double duration);

int _tmain(int argc, TCHAR *argv[])
{
    TCHAR* cWinDir = new TCHAR[MAX_PATH];
    memset(cWinDir, sizeof(TCHAR) * MAX_PATH, 0);

    printf("argc:   %d\n", argc);

    int step = 1;
    double duration = 0;
    if (argc > 1)
    {
        if (argv[1][0] == TCHAR('-') && argv[1][1] == TCHAR('t') && argv[1][2] == TCHAR('\0'))
        {
            step = 3;
            duration = atof((char*)argv[2]);
        }
    }
    //printf("printf content start: %ls\n", argv[1]);
    int j = 0;
    for (int i = 0, h = 0; i < argc - step; i++)
    {
        wcscpy_s(cWinDir + j, MAX_PATH - j, argv[i + step]);
        for (h = 0; argv[i + step][h] != TCHAR('\0'); h++);
        j += h;
        cWinDir[j++] = ' ';
        //printf("%d : %d\n", i, j);
        //printf("printf content start: %ls\n", cWinDir);
    }
    cWinDir[j - 2] = TCHAR('\0');
    //printf("printf content start: %ls\n", cWinDir);

    newProcess(cWinDir,duration);

    return 0;
}


void printTime(SYSTEMTIME* start, SYSTEMTIME* end)
{
    int hours = end->wHour - start->wHour;
    int minutes = end->wMinute - start->wMinute;
    int seconds = end->wSecond - start->wSecond;
    int ms = end->wMilliseconds - start->wMilliseconds;
    if (ms < 0)
    {
        ms += 1000;
        seconds -= 1;
    }
    if (seconds < 0)
    {
        seconds += 60;
        minutes -= 1;
    }
    if (minutes < 0)
    {
        minutes += 60;
        hours -= 1;
    }
    //由於僅考慮在一天之內,不考慮小時會變成負數的情況
    printf("runtime: %02dhours %02dminutes %02dseconds %02dmilliseconds\n", hours, minutes, seconds, ms);
}

void newProcess(TCHAR* cWinDir, double duration)
{
    PROCESS_INFORMATION pi;
    STARTUPINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    

    SYSTEMTIME start_time, end_time;
    memset(&start_time, sizeof(SYSTEMTIME), 0);
    memset(&end_time, sizeof(SYSTEMTIME), 0);
    GetSystemTime(&start_time);

    if (CreateProcess(
        NULL,       //lpApplicationName.若為空,則lpCommandLine必須指定可執行程式
                    //若路徑中存在空格,必須使用引號框定
        cWinDir,    //lpCommandLine
                    //若lpApplicationName為空,lpCommandLine長度不超過MAX_PATH
        NULL,       //指向一個SECURITY_ATTRIBUTES結構體,這個結構體決定是否返回的控制代碼可以被子程序繼承,程序安全性
        NULL,       //  如果lpProcessAttributes引數為空(NULL),那麼控制代碼不能被繼承。<同上>,執行緒安全性
        false,      //  指示新程序是否從呼叫程序處繼承了控制代碼。控制代碼可繼承性
        0,          //  指定附加的、用來控制優先類和程序的建立的識別符號(優先順序)
                    //  CREATE_NEW_CONSOLE  新控制檯開啟子程序
                    //  CREATE_SUSPENDED    子程序建立後掛起,直到呼叫ResumeThread函式
        NULL,       //  指向一個新程序的環境塊。如果此引數為空,新程序使用呼叫程序的環境。指向環境字串
        NULL,       //  指定子程序的工作路徑
        &si,        //  決定新程序的主窗體如何顯示的STARTUPINFO結構體
        &pi         //  接收新程序的識別資訊的PROCESS_INFORMATION結構體。程序執行緒以及控制代碼
    ))
    {
    }
    else
    {
        printf("CreateProcess failed (%d).\n", GetLastError());
        return;
    }


    //wait untill the child process exits
    if (abs(duration - 0) < 1e-6)
        WaitForSingleObject(pi.hProcess, INFINITE);//這裡指定執行時間,單位毫秒
    else
        WaitForSingleObject(pi.hProcess, duration * 1000);

    GetSystemTime(&end_time);

    printTime(&start_time, &end_time);

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}

測試程式

#include <iostream>
#include <Windows.h>
using namespace std;
int main(int argc, char* argv[])
{
    for (int n = 0; n < argc; n++)
    {
        printf("arg[%d]:%s\n", n, argv[n]);
    }
    Sleep(5*1000);
    return 0;
}

測試

  1. 自行編寫程式測試

  2. 系統程式測試

  3. 新增至環境變數

參考資料

Windows

  • CreateProcess

Linux

  • 程序控制

  • 程序控制,linux環境

  • Linux父程序建立子程序的方法

  • Linux建立子程序執行任務

  • execv Linux man page

  • execv函式的應用