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节。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值