c语言初始化字符串 函数 manment,[转载]3.09进程(C语言班最后一天的课程)
1,進程:是容器,是內存上的概念。線程是CPU的概念。
2,fork的作用是根據一個現有的進程復制出一個新進程,原來的進程稱為父進程(Parents Process),新進程稱為子進程(Child Process)。
系統中同時運行著許多進程,這些進程都是從最初只有一個進程開始一個一個復制出來的。
分進程函數fork(),用來開辟子進程。執行fork()之后,會有兩個進程,一個是原來的那個,一個是新生成的子進程。這兩個進程都會執行程序下面的代碼。pid = fork();pid得到的是返回子進程的id,不是自己的id。
1.c
#include
#include
#include
#include
int main(void)
{
pid_t pid = fork();
if(pid < 0){
perror("fork");
exit(0);
}
if(pid > 0){
printf("Parents!n");
}
if(0 == pid){
printf("Child!n");
}
printf("Hello world!n");
return 0;
}
3,先讓父進程睡1秒,會先執行子進程。
2.c
#include
#include
#include
#include
int main(void)
{
pid_t pid = fork();
if(pid < 0){
perror("fork");
exit(0);
}
if(0 == pid){
printf("Child!n");
}else{
sleep(1);
printf("Parents!n");
}
return 0;
}
如果父進程結束后,sleep(1);子進程會打印到終端。
3.c
#include
#include
#include
#include
int main(void)
{
pid_t pid = fork();
if(pid < 0){
perror("fork");
exit(0);
}
if(0 == pid){
sleep(1);
printf("Son!n");
}else{
printf("Father!n");
}
return 0;
}
4,進程號:pid 。getppid()是得到當前進程父進程的pid;getpid()是得到當前進程的pid。
如果子進程getppid()時,父進程已經死掉,子進程就會被托管,那getppid()的值就為1。
所以父進程要sleep(1),這樣子進程getppid()時,得到的就是此進程父進程的pid。
父進程getppid()時,得到的是終端的pid。
4.c
#include
#include
#include
#include
int main(void)
{
pid_t pid = fork();
if(pid < 0){
perror("fork");
exit(0);
}
if(0 == pid){
printf("Child!ngetppid=%d getpid=%d pid=%dn", getppid(),
getpid(), pid);
}else{
printf("Parents!ngetppid=%d getpid=%d pid=%dn", getppid(),
getpid(), pid);
sleep(1);
}
return 0;
}
5,ps查看進程;
ps -jas 查看所有的進程。
6,探討怎么讓父進程知道子進程結束?
exit(0)不會結束整個程序,只會結束一個進程。wait(NULL)等待自己子進程的結束。
父進程執行到wait()時,會進入休眠,子進程結束時,會發一個信號,wait()會捕捉到這個信號,以此來喚醒父進程。
5.c
#include
#include
#include
#include
int main(void)
{
pid_t pid = fork();
if(pid < 0){
perror("fork");
exit(0);
}
if(0 == pid){
sleep(3);
printf("Child!n");
exit(0);
}
wait(NULL);
printf("Parents!n");
return 0;
}
7,探討父進程與子進程關于變量使用的問題?
在fork()之后,會把原來的內存完全復制一份,但不管怎么樣更改,都只會各改各的,互不干擾。
6.c
#include
#include
#include
#include
int main(void)
{
int a = 5;
int * p = &a;
pid_t pid = fork();
if(pid < 0){
perror("fork");
exit(0);
}
if(pid > 0){
*p = 6;
printf("Parents:%dn", *p);
}else{
printf("Child:%dn", *p);
}
return 0;
}
8,怎么樣殺死進程?
下面的程序是個死循環,只能重新開一個終端,執行“kill -9 進程號”。
如果執行以下命令,一定要刪除交換文件:
$vi 5.c //進入5.c 此進程進程號為3456
$Ctrl + z
//暫停當前程序運行
$kill -9 3456 //殺死vi 5.c,這個進程
$fg
//回到剛暫停的程序中,會報錯,因為那個進程已經被殺死了。要刪除那個交換文件才能執行這個操作。
$ls -a//查看所有文件,能把交換文件查看出來。
$rm -fr ..5.c.swap. //刪除交換文件。
$fg
7.c
#include
#include
#include
#include
int main(void)
{
pid_t pid = fork();
if(pid < 0){
perror("fork");
exit(0);
}
if(0 == pid){
while(1){
printf("Child!n");
}
}else{
printf("Parents!n");
}
return 0;
}
9,exec族:用來代替原來進程,執行的時候完全代替,不會再回來。
#include
所需頭文件
(1)execl("/bin/ls", "ls", "-l",
NULL); 后面一定要加NULL,表示參數傳完了。
int
execl(const char *path, const char *arg0, ... )
8.c
#include
#include
#include
#include
int main(void)
{
int ret = execl("/bin/ls", "ls", "-l", NULL);
if(ret < 0){
perror("execl!n");
exit(0);
}
printf("Hello world!n");
return 0;
}
(2)
execlp已經安裝過的程序,不需要寫路徑。
int
execlp(const char *file, const char *arg0, ... );
9.c
#include
#include
#include
#include
int main(void)
{
int ret = execlp("ls", "ls", "-l", NULL);
if(ret < 0){
perror("execl!n");
exit(0);
}
printf("Hello world!n");
return 0;
}
(3)int execv(const char *path, char
*const argv[]);
int
execvp(const char *file, char *const argv[]);
10.c
#include
#include
#include
#include
int main(void)
{
char * buf[5] = {};
buf[0] = "ls";
buf[1] = "-l";
buf[2] = NULL;
int ret = execv("/bin/ls", buf);
// int ret =
execvp("ls", buf);
if(ret < 0){
perror("execv");
exit(0);
}
return 0;
}
10,whereis ls 查看ls的路徑。
11,-rwx-rwx-rwx:普通文件
drwx-rwx-rwx:d代表是一個文件夾
lrwx-rwx-rwx:l代表是一個快捷方式
12,cd是shell的內置命令。如果輸入等于cd,則用chdir(),它并沒有fork()子進程,是父進程在執行chdir()。
11.c
#include
#include
#include
int main(void)
{
chdir("dic");
//這個目錄要事先創建好。
mkdir("./ttt");
return 0;
}
如果用execlp(),會達不到想要的結果。
12.c
#include
#include
#include
#include
int main(void)
{
int ret = execlp("cd", "cd", "/", NULL);
if(ret < 0){
perror("execlp");
exit(0);
}
return 0;
}
13,一定要記住(固定格式):切割字符串函數strtok()
13.c
#include
#include
#include
#include
#define BUF 512
#define BUFF 10
int main(void)
{
char buf[BUF] = {};
ssize_t ret = read(0, buf, BUF);
int i = 0;
char * buff[BUFF] = {};
char * p = buf;
while(1){
p = strtok(p, " n");
if(!p)
break;
buff[i++] = p;
p = NULL;
}
for(i = 0; i < BUFF; i++){
if(NULL == buff[i])
break;
printf("%sn", buff[i]);
}
execvp(buff[0], buff);
return 0;
}
14,編寫一個自己的終端
14.c
#include
#include
#include
#include
#define BUF 512
#define BUFF 10
int main(void)
{
char buf[BUF];
char * buff[BUFF];
char * p = buf;
int i = 0;
pid_t pid;
int ret = 0;
while(1){
write(1, "myshell$", 8);
memset(buf, 0,
BUF);
read(0, buf, BUF);
while(1){
p = strtok(p, "n ");
if(!p){
break;
}
buff[i++] = p;
p = NULL;
}
buff[i] = NULL;
if(strcmp(buff[0], "cd") == 0){
chdir(buff[1]);
continue;
}
pid = fork();
if(pid < 0){
perror("fork");
exit(0);
}else if(pid == 0){
ret = execvp(buff[0], buff);
if(ret < 0){
perror("execvp");
exit(0);
}
}else{
wait(NULL);
}
}
return 0;
}
15,仔細探討fork()的用法。
15.c
#include
#include
#include
#include
int main(void)
{
pid_t pid;
char * message;
int n = 0;
pid = fork();
if(pid < 0){
perror("fork");
exit(0);
}
if(0 == pid){
message = "This is the childn";
n = 6;
}else{
message = "This is the parentn";
n = 3;
}
for(;n > 0; n--){
printf("%s", message);
sleep(1);
}
return 0;
}
程序運行順序如下:
(1)父進程初始化。
(2)父進程調用fork,這是一個系統調用,因此進入內核。
(3)內核根據父進程復制出一個子進程,父進程和子進程的PCB信息相同,用戶態代碼和數據也相同。因此,子進程現在的狀態和父進程一樣,做完了初始化,剛調用了fork進入內核,還沒有從內核返回。
(4)現在有兩個一模一樣的進程看起來都調用了fork進入內核等待從內核返回(實際上fork只調用了一次),系統中還有很多別的進程也等待從內核返回。是父進程先返回還是子進程先返回,還是這兩個進程都等待,先去調度執行別的進程,這都不一定,取決于內核的調度算法。
(5)如果某個時刻父進程被調度執行了,從內核返回后就從fork函數返回,保存在變量pid中的返回值是子進程的id,是一個大于0的整數,因此執行下面else分支,然后執行for循環,打印"This is parentn"三次之后終止。
(6)如果某個時刻子進程被調度執行了,從內核返回后就從fork函數返回,保存在變量pid中的返回值是0,因此執行下面的if(0 == pid)分支,然后執行for循環,打印"This is childn"六次這樣之后終止。fork調用把父進程的數據復制一份給子進程,但此后二者互不影響,在這個例子中,fork調用之后父進程和子進程的變量message和n被賦予不同的值,互不影響。
(7)父進程每打印一條信息就睡眠1秒,這時內核調度別的進程執行,在這1秒這么長的間隙里(對于計算機來說1秒很長了)子進程很有可能被調度到。同樣地,子進程每打印一條信息就睡眠1秒,在這1秒期間父進程也很有可能被調度到。所以程序運行地結果基本上是父子進程交替打印,但這也不一定的,取決于系統中其他進程的運行情況和內核的調動算法,如果系統中其他進程非常繁忙則有可能觀察到不同的結果。
(8)這個程序是在Shell下運行的,因此Shell進程是父進程的父進程。父進程運行時Shell進程處于等待狀態,當父進程終止時Shell進程認為命令執行結束了,于是打印Shell提示符,而事實上子進程這時還沒結束,所以子進程的消息打印到了Shell提示符后面。最后光標停在This is the child的下一行,這時用戶仍然可以敲命令,即使命令不是緊跟在提示符后面,Shell也能正確讀取。
fork函數:
(1)fork函數的特點"調用一次,返回兩次",在父進程中調用一次,在父進程和子進程中各返回一次。一開始是一個控制流程,調用fork之后發生了分叉,變成兩個控制流程,這也就是"fork"(分叉)這個名字的由來了。子進程中fork的返回值是0,而父進程中fork的返回值則是子進程的id(從根本上說fork是從內核返回的,內核自有辦法讓父進程和子進程返回不同的值),這樣當fork函數返回后,程序員可以根據返回值的不同讓父進程和子進程執行不同的代碼。
(2)fork的返回值這樣規定也是有道理的。fork在子進程中返回0,子進程仍可以調用getpid函數得到自己的進程id,也可以調用getppid函數得到父進程的id。在父進程中用getpid可以得到自己的進程id,然而要想得到子進程的id,只有將fork的返回值記錄下來,別無他法。
(3)fork的另一個特性是所有由父進程打開的描述符都被復制到子進程中。父、子進程中相同編號的文件描述符在內核中指向同一個file結構體,也就是說file結構體的引用計數要增加。
16,exec函數
用fork創建子進程后執行的是和父進程相同的程序(但有可能執行不同的代碼分支),子進程往往要調用一種exec函數以執行另一個程序。當進程調用一種exec函數時,該進程的用戶空間代碼和數據完全被新程序替換,從新程序的啟動例程開始執行。調用exec并不創建新進程,所以調用exec前后該進程的id并未改變。
exec族
man
3 exec
頭文件:#include
int execl(const char *path, const char *arg0, ... );
int execlp(const char *file, const char *arg0, ... );
int execle(const char *path, const char *arg0, ...);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvP(const char *file, const char *search_path, char *const
argv[]);
man 2 execve
int execve(const char *path, char *const argv[], char *const
envp[]);
這些函數如果調用成功則加載新的程序從啟動代碼開始執行,不再返回,如果調用出錯則返回-1,所以exec函數只有出錯的返回值而沒有成功的返回值。
不帶p(表示path)的exec函數第一個參數必須是程序的相對路徑或絕對路徑,例如"/bin/ls"或"./a.out",而不能是"ls"或"a.out"。對于帶字母p的函數:如果參數中包含/,則將其視為路徑名。否則視為不帶路徑的程序名,在PATH環境變量的目錄列表中搜索這個程序。
帶由字母l(表示list)的exec函數要求將新程序的每個命令行參數都作為一個參數傳給它,命令行參數的個數是可變的,因此函數原型中有…,…中的最有一個可變參數應該是NULL,起sentinel的作用。
對于帶有字母v(表示vector)的函數,則應該先構造一個指向各參數的指針數組,然后將該數組的首地址當作參數傳給它,數組中的最后一個指針也應該是NULL,就像main函數的argv參數或者環境變量一樣。
對于以e(envirnoment)結尾的exec函數,可以把一份新的環境變量傳給它,其他exec函數仍使用當前的環境變量表執行新程序。
事實上,只有execve是真正的系統調用,其他五個函數最終都調用execve,所以execve在man手冊第2節,其他函數在man手冊第3節。
總結
以上是生活随笔為你收集整理的c语言初始化字符串 函数 manment,[转载]3.09进程(C语言班最后一天的课程)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大逆转裁判安卓汉化(大逆转裁判 安卓)
- 下一篇: linuxtcp6改成tcp(linux