Unix学习笔记(三)

3 篇文章 2 订阅

这一部分的重点在于进程。

1. test.c 

源码:

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

int main(){
    printf("%d\n",getpid());
    printf("%d\n",getppid());
}

输出:


这一段代码十分简单,getpid(),getppid()分别获取当前进程和当前进程父进程的进程id。我们关注的是得到的父进程id:11882代表的究竟是什么?

所以我们执行如下命令:

kill -9 11882

得到如下结果:


由此可见,该进程的父进程就是控制台程序。

2. main.c

源码:

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

int glob = 6;
char buf[] = "a write to stdout\n";

int
main(int argc, char **argv) {
	int var;
	pid_t pid;

	var = 88;
	if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1) {
		fprintf(stderr, "%s: write error: %s\n",
				argv[0], strerror(errno));
		exit(1);
	}
	printf("before fork\n");

	if ((pid = fork()) < 0) {
		fprintf(stderr, "%s: fork error: %s\n",
				argv[0], strerror(errno));
		exit(1);
	} else if (pid == 0) { /* child */
                var++;printf("in Child, var is %d\n",var);
                printf("in Child, &var is %p\n",&var);
                char *argv[]={"ls",NULL};
                char *envp[]={NULL};
		execve("../../../../../bin/ls",argv,envp);
		var++;printf("After execve, in Child, var is %d\n",var);
	} else {		/* parent */
		sleep(2);
                printf("in Parent, &var is %p\n",&var);
	}

	printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
	exit(0);
}

输出:


代码分析:

这一段代码稍微复杂一些,重点在于fork()创建新进程的理解。

要注意的是一下几点:

(1) 从输出结果可以看到,execve后面的语句是没有被执行的,execve之后的语句可以被执行当且仅当exeve语句执行失败的时候。

(2) fork()语句执行之后系统会复制一份一模一样的资源给子进程,所以我们看到在子进程中var的值变为了89而回到父进程var的值依然为88。

(3) 既然资源已经被复制,那为什么在父进程和子进程中var的内存地址是一样的呢?

这说明在unix中,每个进程都有各自独立的内存布局,每一个进程都认为自己占有全部的空间,真实的内存地址需要转化才能得到。

(4) sleep(2)是为了保证先执行完子进程,再执行父进程

3. entry.c

源码:

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

int foo(void) {
	printf("Foo for the win!\n");
	return 1;
	/* Note: this will cause a segfault, because this function
	 * returns, but there is nothing to return to: the routines set up
	 * by the kernel remain set up for 'main', we just told the linker
	 * to jump into 'foo' at program start.  Compare objdump(1)
	 * output. */
}

int bar(void) {
	printf("bar rules!\n");
	exit(1);
	/* Unlike foo(), this will not cause a segfault, since we are not
	 * returning; we explicitly call exit(3). */
}

int main(int argc, char **argv) {
	printf("Hooray main!\n");
	/* Note that we do explicitly _not_ return an error here, nor call
	 * any of the exit(3) functions.  Your compiler will warn you
	 * about this. */
}

这个代码本身没有什么好说的,主要是观察代码执行入口的地址,和关于main函数的一些特性。

执行以下命令:

readelf -h a.out


我们可以看到入口地址为0x400470

那么这个地址代表什么呢?

再执行如下命令:

objdump -d a.out|more


可以看到,这段程序的入口为<_start>

注意右侧的 callq 40040<_libc_start_main@plt>我们可以看看这个函数

glibc/csu/lic-start.c

STATIC  int
LIBC_START_MAIN  (int  (*main)  (int,  char  **,  char  **  MAIN_AUXVEC_DECL),
		int  argc,  char  **argv,
		__typeof  (main)  init,
		void  (*fini)  (void),
		void  (*rtld_fini)  (void),  void  *stack_end)
{
[...]
	result  =  main  (argc,  argv,  __environ  MAIN_AUXVEC_PARAM);


	exit  (result);
}

那么,假如我们指定入口函数呢?

输入下面的命令:

gcc -e foo entry.c -o foo

查看输出:


可以看到,程序报错了(段吐核也就是内存错误)

报错原因:从源码中我们看到foo程序return了1,但实际上,在这个程序中没有可以接受这个return值的东西。

同理,如果我们把函数原型改为 int foo(int argc, char**argv),测试,同样可以发现,这几个参数是访问不到的。

因此我们可以得到如下结论,如果想要指定某一个函数作为程序的入口函数,那么这个函数应该满足如下两个条件

A. 该函数不带参数

B. 该函数不带return值(可以用exit(number)进行替代)

4. exit-handlers.c

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

int i;

void
my_exit1(void) {
	printf("first exit handler: %d\n", i);
	i++;
}

void my_exit2(void) {
	printf("second exit handler: %d\n", i);
}


int
main(int argc, char **argv) {
	i = 0;
	if (atexit(my_exit2) != 0) {
		perror("can't register my_exit2\n");
		exit(1);
	}

	if (atexit(my_exit1) != 0) {
		perror("can't register my_exit1");
		exit(1);
	}

	if (atexit(my_exit1) != 0) {
		perror("can't register my_exit1");
		exit(1);
	}

	printf("main is done\n");

	return (0);
}

函数输出:


函数详解:

int atexit (void (*)(void))

在讲这个函数之前先提一下进程退出的方式:

进程终⽌的⽅式有8种,前5种为正常终⽌,后三种为异常终⽌:
main函数返回;
调⽤exit函数;
调⽤_exit_Exit
最后⼀个线程从启动例程返回;
最后⼀个线程调⽤pthread_exit
调⽤abort函数;
接到⼀个信号并终⽌;
最后⼀个线程对取消请求做出响应。

然后再说一下exit()和_exit()的区别:exit函数运行时首先会执行由atexit()函数登记的函数,然后会做一些自身的清理工作,同时刷新所有输出流、关闭所有打开的流并且关闭通过标准I/O函数tmpfile()创建的临时文件,最后调用_exit系统函数。也就是说exit()函数在退出时会调用一些相关函数做善后处理,而_exit()则是直接退出。


int atexit(void (*func)(void)); 

用来指定在执行exit()函数时还要执行的函数。

atexit也被称为登记函数,重点我们要观察登记函数的登记顺序和调用顺序,可以看到登记顺序和调用顺序正好相反,这个应该是一个堆栈。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1章 UNIX操作系统概述 7 1.1 UNIX操作系统简介 7 1.2 UNIX系统组成 7 1.3 UNIX启动过程 8 1.4 UNIX用户登录过程 8 1.5 与UNIX有关的几个名词 9 第2章 UNIX基本常识 11 2.1 启动终端 11 2.2 登录 11 2.3 初始化文件 11 2.4 注销(退出UNIX系统) 12 第3章 UNIX文件系统 13 3.1 文件系统分类 13 3.2 文件类型 13 3.2.1 正规文件 13 3.2.2 目录文件 14 3.2.3 字符和块设备文件 15 3.2.4 套接字文件 15 3.2.5 命名管道文件 16 3.2.6 链接文件 16 3.3 树型目录结构 16 3.4 文件和目录的命名 18 3.5 UNIX文件存取权限 18 3.6 重定向与管道 21 3.6.1 UNIX重定向 21 3.6.2 UNIX管道 22 3.7 常用配置文件 22 3.7.1 /etc/passwd文件 22 3.7.2 /etc/group文件 22 3.7.3 /etc/hosts 23 3.7.4 /etc/services 23 3.8 文件系统管理 23 3.8.1 mount 23 3.8.2 umount 24 3.8.3 加载配置文件 24 3.8.4 fsck 25 第4章 UNX系统常用命令 27 4.1 UNIX命令基础 27 4.1.1 UNIX命令的一般格式 27 4.1.2 特殊功能键和字符 28 4.1.3 查看帮助信息 30 4.1.4 在后台运行程序 31 4.1.5 在指定时间执行指定程序 31 4.2 vi编辑器的最基本用法 32 4.2.1 vi简介 32 4.2.2 vi的启动和退出 33 4.2.3 vi的两种模式 33 4.2.4 vi的基本操作 34 4.2.5 vi的高级操作 36 4.3 目录操作命令 38 4.3.1 pwd 38 4.3.2 mkdir 38 4.3.3 cd 38 4.3.4 rmdir 39 4.4 文件操作命令 39 4.4.1 ls 39 4.4.2 cat 40 4.4.3 head 41 4.4.4 tail 41 4.4.5 more 41 4.4.6 cp 43 4.4.7 mv 44 4.4.8 rm 44 4.4.9 chmod 44 4.4.10 chown 46 4.4.11 chgrp 46 4.4.12 cmp 46 4.4.13 diff 47 4.4.14 wc 47 4.4.15 split 47 4.4.16 touch 48 4.4.17 file 48 4.4.18 pack 48 4.4.19 pcat 49 4.4.20 unpack 49 4.4.21 find 49 4.4.22 grep 51 4.4.23 pg 52 4.4.24 ln 52 4.4.25 sort 53 4.4.26 compress 53 4.4.27 uncompress 54 4.4.28 gzip 54 4.4.29 gunzip 54 4.4.30 tar 54 4.4.31 cpio 55 4.4.32 tee 56 4.5 状态信息命令 57 4.5.1 w 57 4.5.2 who 57 4.5.3 whodo 57 4.5.4 logname 58 4.5.5 whoami 58 4.5.6 whereis 58 4.5.7 which 58 4.5.8 date 58 4.5.9 cal 59 4.5.10 time 59 4.5.11 id 59 4.5.12 hostid 60 4.5.13 hostname 60 4.5.14 df 60 4.5.15 du 60 4.5.16 stty 61 4.5.17 tty 61 4.5.18 history 61 4.5.19 alias 61 4.5.20 echo 62 4.5.21 uname 62 4.5.22 clear 62 4.6 网络命令 62 4.6.1 arp 62 4.6.2 finger 63 4.6.3 wall 63 4.6.4 mesg 63 4.6.5 write 63 4.6.6 ping 63 4.6.7 netstat 64 4.6.8 telnet 64 4.6.9 ftp 64 4.7 进程管理命令 65 4.7.1 kill 65 4.7.2 ps 66 4.7.3 sleep 68 4.7.4 nice 68 4.7.5 shutdown 68 4.7.6 halt 69 4.7.7 poweroff 69 4.7.8 reboot 69 4.8 用户管理命令 69 4.8.1 su 69 4.8.2 groupadd 69 4.8.3 groupdel 70 4.8.4 useradd 70 4.8.5 userdel 70 4.8.6 passwd 71 第5章 shell的基础知识 72 5.1 什么是shell 72 5.2 别名化 73 5.3 shell变量 74 5.3.1 shell变量的存储机制 74 5.3.2 变量替换 74 5.3.3 命令替换 76 5.4 用户环境 76 5.5 两个重要的环境变量 77 5.6 shell启动文件 78 5.7 监控进程 78 第6章 附录 79 6.1 ftp命令参考 79 6.2 vi命令参考 80 6.3 find命令详解 85 6.3.1 Find命令形式 85 6.3.2 Find命令参数 85 6.3.3 Find命令举例 87 6.4 grep命令详解 99
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值