Unix C (七)

進程:
1、程序和進程的區別。
1)程序就是代碼編譯鏈接的成品(可執行程序)。程序是硬盤上的文件。
2)進程就是運行在內存中的程序,一個程序可以啓動多次,得到多個進程。
3)CPU只能直接操作內存,不能直接操作硬盤,硬盤上的程序想要運行,程序必須先加載到內存中,變成進程。
4)在日常生活中,爲了跟客戶交互,有時候也把進程成爲程序。

2、主流的操作系統都是多進程的,每個進程內部還可以用多線程實現功能的並行。
3、Unix/Linux進程相關的命令:
ps 只能看到當前終端啓動的程序
ps -aux Linux專用查看所有進程的命令,Unix不直接支持。
ps -ef Unix/Linux查看所有進程的命令。
常用管道命令:管道的作用就是用前面的的輸出作爲後面的輸入。
ps -ef | wc 統計進程數的多少
ls -al | more 分頁顯示當前目錄的所有內容。
kill -9 進程號 殺死指定進程號的進程
pkill 進程名 殺死指定進程
4、父進程和子進程:如果a進程啓動了b進程,a就是父進程,b就是子進程。
5、進程的狀態:每個進程都有自己的狀態,主要包括:
處於休眠狀態,大多數進程處於休眠狀態
有子進程
正在運行的進程
殭屍進程(進程已經結束,但是資源沒有回收的進程)
暫停或被追蹤
< 優先級高的進程
6、Unix/Linux系統的啓動順序是:系統啓動0進程,0進程啓動進程1或者進程2,其他進程都是由進程1或者2以及它的子進程啓動的。


進程的相關函數:
1、每個進程用進程ID(PID)做唯一標識,PID是系統管理的。通過函數getpid()可以取得進程的ID。如果進程結束,PID可以重複使用,但是要延遲重用。
getpid() 取當前進程的PID
getppid() 取父進程的PID

getuid()取當前用戶的ID


實例:

/*
   進程ID演示
 */


#include <stdio.h>


int main(){
printf("PID = %d\n",getpid());
printf("PPID = %d\n",getppid());
printf("UID = %d\n",getuid());
printf("GID = %d\n",getgid());


getchar();


return 0;
}



如何創建子進程:
1、fork() 通過複製父進程創建子進程
2、vfork()+execl() 不復制任何東西,創建一個全新的子進程。
3、進程PID用pid_t類型,是一個非負整數。
函數原型:pid_t fork(void);返回子進程的PID或者0,錯誤返回-1.
4、fork()是通過複製父進程的內存空間創建的子進程,除了代碼區父子進程共享(只讀區),其他內存區域子進程都要複製(存在讀寫操作)。
5、fork()創建子進程後,父子進程同時運行,但誰先運行不確定,誰先結束不確定。
6、fork()在複製父進程的內存空間時,如果遇到文件描述符,複製文件描述符,但不復制文件表。
7、fork()在複製父進程的內存空間時,也會複製輸出/輸入緩衝區。
8、fork()函數調用一次,返回兩次。父進程返回一次,子進程也返回一次。父進程返回子進程的PID,子進程返回0。


關於父子進程的運行和資源回收:
1、父進程啓動後,父子進程同時運行。如果子進程先結束,會給父進程發信號,父進程負責回收子進程的資源。
2、父進程啓動後,父子進程同時運行。如果父進程先結束,子進程變成孤兒進程,認進程1(init)做新的父進程,init負責回收它們的資源,init進程也叫孤兒院。
3、父進程啓動子進程後,父子進程同時運行。如果子進程沒有給父進程發信號就結束,或者父進程沒有及時處理信號,此時子進程就變成殭屍進程(進程已經結束,但是資源沒有回收的的“進程”)。
4、fork()函數創建子進程時,代碼區可分爲三個部分:fork()之前的代碼,fork()的代碼以及fork()之後的代碼。
fork()之前的代碼,只有父進程執行一次
fork()的代碼,部分執行一次,部分執行兩次,return執行兩次,一次是父進程返回子進程的PID,一次是子進程返回0。

fork()之後的代碼,父子進程都執行一次。
  6、fork() 父子進程是使用相同的代碼區,如果 需要父子進程代碼區不同的話,可以使用 vfork()+execl()。
  7、vfork() 創建新的子進程,execl()負責提供子進程的代碼和數據(程序)。
  8、execl()函數是用新的程序替換原有的程序。
  9、vfork() 從語法上和fork()完全一樣,區別在於vfork()不復制任何父進程的資源。vfork()會搶父進程的資源,導致父進程阻塞。父進程解除阻塞的條件:
                1 子進程結束時,歸還父進程的資源(無並行)。
                2 子進程調用exec系列函數(execl等),也歸還父進程資源。
               注意:
                            vfork()確保子進程先運行(父進程沒資源),調用execl()之後父子進程同時運行。
                            vfork()創建的子進程必須用exit()退出。
  10、execl()可以用一個新程序替換舊程序,但不新建任何的進程。如果新的程序正常啓動,舊程序不再繼續運行;如果新的程序啓動失敗,舊程序繼續運行。
  11、execl(程序所在的路徑,命令,選項,命令參數,NULL) ,啓動失敗返回-1.

實例:

(1)

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>


int i1 = 10;
int i2;


int main(){
int i3 = 10;
int* pi = (int *)malloc(sizeof(int));
if(!pi){
perror("malloc"),exit(-1);
}
*pi = 10;


pid_t pid = fork();
if(!pid){
i1 = 20;
i2 = 20;
i3 = 20;
*pi = 20;


printf("c:i1 = %d, i2 = %d, i3 = %d, *pi = %d\n",i1,i2,i3,*pi);
printf("c:%p,%p,%p,%p\n",&i1,&i2,&i3,pi);
free(pi);
pi = NULL;
exit(0);   //結束子進程
}
sleep(1);   //父進程休眠一秒,保證子進程先運行
printf("p:i1 = %d, i2 = %d, i3 = %d, *pi = %d\n",i1,i2,i3,*pi);
printf("p:%p,%p,%p,%p\n",&i1,&i2,&i3,pi);


free(pi);
pi = NULL;


return 0;
}


(2)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>


int main(){
  pid_t pid = fork();   //f父子進程同時打開同一個文件,打開兩次
  int fd = open("a.txt",O_RDWR|O_CREAT,0666);
  if(fd == -1) perror("open"),exit(-1);
  //pid_t pid = fork();  //父子進程共享同一個文件,打開一次
  if(pid == 0){
    char c;
for(c='a';c<='z';c++){
  write(fd,&c,1);
  usleep(1);
}
close(fd);
exit(0);
  }
  char c;
  for(c='A';c<='Z';c++){
  write(fd,&c,1);
  usleep(1);
  }
  close(fd);
  return 0;
}


(3)

源代碼文件1:

/*************************************************************************
    > File Name: wc.c
    > Author: Andy001847
    > Mail: [email protected]
    > Created Time: 2014年11月05日 星期三 14時07分42秒
 ************************************************************************/

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]){
    int num = 0;
    
    for(num = 1; num < argc; num++){    //忽略第一個命令,從第一個有意義的字符串開始
        int len = strlen(argv[num]);    //調用求字符串有效長度的函數strlen
        printf("%d\n",len);        //依次輸出每個字符串的有效長度
    }
    return 0;
}


源代碼文件二:

/*************************************************************************
    > File Name: execute.c
    > Author: Andy001847
    > Mail: [email protected]
    > Created Time: 2014年11月05日 星期三 14時23分25秒
 ************************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[]){
    int num = 0;
    for(num = 1; num < argc; num++){
        pid_t pid = vfork();    //每次都創建子進程
        if(!pid){          //子進程
            int res = execl("./wc","./wc",argv[num],NULL);    //調用execl系列函數之一
            if(res == -1){        //差錯處理
                perror("execl"),exit(-1);
            }
        }
        //sleep(30);        //休眠30秒
    }

    return 0;
}


知識回顧:
刷新緩衝區的條件:
1、遇到換行\n
2、緩衝區滿了
3、程序結束了
4、調用函數fflush()人工刷新。


進程的結束:
1、進程結束的方式分爲正常結束和非正常結束。
正常結束包括:
1、主函數中執行了return,非主函數執行return不結束進程,只結束自己的函數。
2、執行exit()。
3、執行_Eixt和_exit()。
4、所有線程都結束。
非正常結束包括:
1、信號打斷進程(Ctrl+c, kill -9)。
2、最後一個線程被取消。
2、exit()和_Exit()/_exit()的區別。
1、_Exit()和_exit()基本無區別,唯一的區別就是_Exit()是標準C的函數,而_exit()是Unix C中的函數。

2、exit()不是立即退出,甚至可以先執行在atexit()函數中註冊

實例:

/*
   exit(),_Exit()和_exit()函數演示
*/


#include <stdio.h>
#include <stdlib.h>


void fa(){
printf("fa() is called\n");
}


int main(){
atexit(fa); //只負責註冊,不負責調用
printf("Begin\n");
//exit(0); //不會立即結束,會調用atexit裏的函數後結束
//return 0; //在主函數中執行return和執行exit效果一樣
//_exit(0); //立即結束程序
printf("End\n");
}


3、函數wait()/waitpid()可以讓父進程等待子進程結束,並取得子進程的退出狀態和退出碼(主函數的return或exit的值)。
函數原型:pid_t wait(int *status);
函數原理:wait()函數讓父進程等待任意一個子進程的結束,並返回結束的子進程的PID,把結束子進程的退出狀態和退出碼存入status中。如果沒有子進程結束,會阻塞父進程,直到所有子進程結束位置。包括殭屍子進程,因此wait()函數也叫殮屍工。

4、宏函數WIFEXITED(status)判斷進程是否正常結束,而WEXITSTATUS(status)可以獲得進程的退出碼。

 5、waitpid() 可以設置等待的方式和等待的子進程。
  函數原型:pid_t waitpid(pid_t pid,int* status,  int options)
       參數:

                 pid可以指定等待哪個/哪些子進程

                         pid的值:
                                       -1 : 等待任意子進程,和wait()一樣
                                       >0 :  等待子進程的ID=pid(特指)
                                        0 : 等待本組子進程(與父進程相同進程組)
                                        <-1: 等待進程組爲|pid|的所有子進程
                        注:-1 和 >0 常用,後面兩個瞭解即可。
                 status用法和wait一樣
                 options可以設置非阻塞的等待(不等待)
                        options 爲0 , 沒有子進程結束繼續等待
                                      爲WNOHANG,沒有子進程結束不等待,直接返回0.
  
 返回有三種可能:
   結束子進程的pid
   -1 代表出錯
   0 只有在options爲WNOHANG時可能返回,代表沒有子進程結束,也沒有出錯。


實例:

(1)

/*
   wait函數演示
 */


#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>


int main(){
pid_t pid = fork();
if(!pid){ //子進程
printf("子進程%d運行\n",getpid());
sleep(1);
printf("子進程%d結束\n",getpid());
// exit(100);
return 100;
}
int status = 0;
pid_t wpid = wait(&status);
printf("結束的子進程是%d\n",wpid);
if(WIFEXITED(status)){
printf("退出碼:%d\n",WEXITSTATUS(status));
printf("父進程結束\n");
}


return 0;
}

(2)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>


int main(){
  pid_t pid1,pid2;
  pid1 = fork();
  if(pid1>0)   //父進程
   pid2 = fork();
  if(pid1 == 0){  //子進程
     printf("子進程1開始執行\n");
sleep(3);
printf("子進程1結束\n");
exit(100);
  }
  if(pid2 == 0){  //子進程
    printf("子進程2開始執行\n");
sleep(1);
printf("子進程2結束\n");
exit(200);
  }
  printf("父進程開始執行\n");
  int status;
  waitpid(pid1,&status,0);//等待子進程pid1結束,並得到狀態碼
  printf("返回:%d\n",WEXITSTATUS(status));
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章